Реализация главного файла

realizaciya glavnogo fajla Примеры законченных программ с графическим интерфейсом

Наконец, модуль в примере 11.4 представляет собой реализацию PyEdit. Этот файл может запускаться как самостоятельный сценарий или импортироваться другими приложениями. Его программный код организован по пунктам главного меню. Главные классы, используемые для запуска и встраивания объекта PyEdit, находятся в конце файла. Во время экспериментов с PyEdit изучайте этот листинг, чтобы разобраться в его возможностях и используемых приемах.

Пример 11.4. PP4E\Gui\TextEditor\textEditor.py

############################################################################## PyEdit 2.1: Текстовый редактор и компонент на Python/tkinter.

Использует текстовый виджет из библиотеки Tk, меню и панель инструментов GuiMaker для реализации полнофункционального текстового редактора, который может выполняться, как самостоятельная программа, или прикрепляться к другим графическим интерфейсам, как компонент. Используется также в PyMailGUI и PyView для редактирования сообщений электронной почты и примечаний к файлам изображений. Кроме того, используется в PyMailGUI и PyDemos во всплывающем режиме для отображения текстовых файлов и файлов с исходными текстами.

Новое в версии 2.1 (4 издание)

  работает под управлением Python 3.X (3.1)

  добавлен пункт grepменю и диалог: многопоточный поиск в файлах

  проверяет все окна на наличие несохраненных изменений при завершении

  поддерживает произвольные кодировки для файлов: в соответствии с настройками в файле textConfig.py

  переработаны диалоги поиска с заменой и выбора шрифта, чтобы обеспечить возможность одновременного вывода нескольких диалогов

  вызывает self.update() перед вставкой текста в новое окно

  различные улучшения в реализации операции Run Code, как описывается в следующем разделе

2.1  улучшения в реализации операции Run Code:

  после команды chdir использует базовое имя запускаемого файла, а не относительные пути

  в Windows использует инструмент запуска, поддерживающий передачу аргументов командной строки

  операция Run Code наследует преобразование символов обратного слеша от модуля launchmodes (необходимость в этом уже отпала)

Новое в версии 2.0 (3 издание)

  добавлен простой диалог выбора шрифта

  использует прикладной интерфейс Tk 8.4 к стеку отмен, чтобы добавить поддержку отмены/возврата (undo/redo) операций редактирования

  запрос подтверждения при выполнении операций Quit, Open, New, Run выполняется, только если имеются несохраненные изменения

  поиск теперь по умолчанию выполняется без учета регистра символов

  создан модуль с настройками для начальных значений шрифта/цвета/размера/чувствительности к регистру при поиске

TBD[XLVI] (и предложения для самостоятельной реализации):

  необходимость учета регистра символов при поиске можно было бы задавать в графическом интерфейсе (а не только в файле с настройками)

  при поиске по файлу или в операции Grep можно было бы использовать поддержку регулярных выражений, реализованную в модуле re (см. следующую главу)

  можно было бы попробовать реализовать подсветку синтаксиса (как в IDLE или в других редакторах)

  можно было бы попробовать проверить завершение работы программы методом quit() в неподконтрольных окнах

  можно было бы помещать в очередь каждый результат, найденный в диалоге Grep, чтобы избежать задержек

  можно было бы использовать изображения на кнопках в панели инструментов (как в примерах из главы 9)

  можно было бы просматривать строки, чтобы определить позицию вставки Tk для оформления отступов в окне Info

   можно было бы поэкспериментировать с проблемой кодировок в диалоге grep

(смотрите примечания в программном коде);

##############################################################################

Version = ‘2.1’

import sys, os # платформа, аргументы,

#   инструменты запуска

from tkinter import * # базовые виджеты, константы

from tkinter.filedialog import Open, SaveAs # стандартные диалоги from tkinter.messagebox import showinfo, showerror, askyesno from tkinter.simpledialog import askstring, askinteger from tkinter.colorchooser import askcolor

from PP4E.Gui.Tools.guimaker import * # Frame + построители

#   меню/панелей инструментов

   общие настройки

try:

import textConfig # начальный шрифт и цвета

configs = textConfig.__dict__ # сработает, даже если модуль отсутствует в except: # пути поиска или содержит ошибки

configs = {}

helptext = “””PyEdit, версия %s

апрель, 2010

(2.0: январь, 2006)

(1.0: октябрь, 2000)

Программирование на Python, 4 издание

Марк Лутц (Mark Lutz), для издательства OReilly Media, Inc.

Программа и встраиваемый компонент текстового редактора, написанный на Python/tkinter. Для быстрого доступа к операциям использует отрывные меню, панели инструментов и горячие клавиши в меню.

Дополнения в версии %s:

   поддержка 3.X

   новый диалог grepпоиска во внешних файлах

   проверка несохраненных изменений при завершении

   поддержка произвольных кодировок для файлов

  допускает одновременный вывод нескольких диалогов поиска с заменой и выбора шрифта

   различные улучшения в операции Run Code

Дополнения в предыдущей версии:

   диалог выбора шрифта

   неограниченное количество отмен/возвратов

  quit/open/new/run предлагают сохранить, только если есть несохраненные изменения

   поиск выполняется без учета регистра символов

   модуль с начальными настройками textConfig.py

START = ‘1.0’ # индекс первого символа: строка=1,столбец=0

SEL_FIRST = SEL + ‘.first # отобразить тег sel в индекс

SEL_LAST = SEL + ‘.last’ # то же, что ‘sel.last’

FontScale = 0 # использовать увеличенный шрифт в Linux

if sys.platform[:3] != ‘win’: # и в других неWindows системах

FontScale = 3

############################################################################## # Главные классы: реализуют графический интерфейс редактора, операции

   разновидности GuiMaker должны подмешиваться в более специализированные

   подклассы, а не наследоваться непосредственно, потому что этот класс

   принимает множество форм.

##############################################################################

class TextEditor: # смешать с классом Frame, имеющим меню/панель инструментов startfiledir = ‘.’ # для диалогов editwindows = [] # для проверки при завершении

#   Настройки порядка выбора кодировки

#   импортируется в класс, чтобы обеспечить возможность переопределения в # подклассе

if __name__ == ‘__main__’:

from textConfig import ( # мой каталог в пути поиска

opensAskUser, opensEncoding, savesUseKnownEncoding, savesAskUser, savesEncoding) else:

from .textConfig import ( # 2.1: всегда из этого пакета opensAskUser, opensEncoding, savesUseKnownEncoding, savesAskUser, savesEncoding)

ftypes = [(‘All files’, ‘*’), # для диалога открытия файла

(‘Text files’, ‘.txt’), # настроить в подклассе или

(‘Python files’, ‘.py’)]

# устанавливать в каждом экземпляре

colors = [{‘fg’

:’black’, ‘bg’

:’white’}, # список цветов для выбора

{‘fg’

:’yellow’, ‘bg’

:’black’}, # первый элемент по умолчанию

{‘fg’

:’white’, ‘bg’

:’blue’}, # переделать по-своему или

{‘fg’

:’black’, ‘bg’

:’beige’}, # использовать элемент выбора

{‘fg’

:’yellow’, ‘bg’

:’purple’},# PickBg/Fg

{‘fg’

:’black’, ‘bg’

:’brown’},

{‘fg’

:’lightgreen’, ‘bg’

:’darkgreen’},

{‘fg’

:’darkblue’, ‘bg’

:’orange’},

{‘fg’

:’orange’, ‘bg’

:’darkblue’}]


 


fonts = [(‘courier’, (‘courier’, (‘courier’, (‘courier’, (‘times’, (‘helvetica’ (‘ariel’, (‘system’, (‘courier’,


9+FontScale,

‘normal’),

#

12+FontScale,

‘normal’),

#

10+FontScale,

‘bold’),

#

10+FontScale,

‘italic’),

#

10+FontScale,

‘normal’),

#

10+FontScale,

‘normal’),

#

10+FontScale,

‘normal’),

#

10+FontScale,

20+FontScale,

‘normal’), ‘normal’)]

#

 


шрифты, нейтральные в отношении платформы (семейство, размер, стиль) или вывести в списке увеличить в Linux использовать

‘bold italic’ для 2 а также ‘underline’


 

 

 

 

 


def __init__(self, loadFirst=’’, loadEncode=’’):

if not isinstance(self, GuiMaker):

raise TypeError(‘TextEditor needs a GuiMaker mixin’)

self.setFileName(None)

self.lastfind = None

self.openDialog = None

self.saveDialog = None

self.knownEncoding = None # 2.1 кодировки: заполняется Open или Save

self.text.focus() # иначе придется щелкнуть лишний раз

if loadFirst:

self.update() # 2.1: иначе строка 2;

self.onOpen(loadFirst, loadEncode) # см. описание в книге



(‘Paste’, ‘separator’,

0,

self.onPaste),

(‘Delete’,

0,

self.onDelete),

(‘Select All’,

0,

self.onSelectAll)]

),

(‘Search’, 0,

 

 

[(‘Goto…’,

0, self.onGoto),

 

(‘Find…’,

0, self.onFind),

 

(‘Refind’,

0, self.onRefind),

 

(‘Change…’,

0, self.onChange),

 

(‘Grep…’,

3, self.onGrep)]

),

 

 

 

(‘Tools’, 0,

 

 

 

[(‘Pick Font

’, 6,

self.onPickFont),

 

(‘Font List’,

0,

self.onFontList),

 

‘separator’,

 

 

 

(‘Pick Bg…’,

3,

self.onPickBg),

 

(‘Pick Fg…’,

0,

self.onPickFg),

 

(‘Color List’,

0,

self.onColorList),

 

‘separator’,

 

 

 

(‘Info…’,

0,

self.onInfo),

 

(‘Clone’,

1,

self.onClone),

 

(‘Run Code’,

0,

self.onRunCode)]

)] self.toolBar = [

 

(‘Save’,

self.onSave,

{‘side’

: LEFT}),

(‘Cut’,

self.onCut,

{‘side’

: LEFT}),

(‘Copy’,

self.onCopy,

{‘side’

: LEFT}),

(‘Paste’,

self.onPaste,

{‘side’

: LEFT}),

(‘Find’,

self.onRefind,

{‘side’

: LEFT}),

(‘Help’,

self.help,

{‘side’

: RIGHT}),

(‘Quit’,

self.onQuit,

{‘side’

: RIGHT})]

 

def makeWidgets(self): # вызывается из GuiMaker.__init__

name = Label(self, bg=’black’, fg=’white’) # ниже меню, выше панели name.pack(side=TOP, fill=X) # компоновка меню/панелей

#   фрейм GuiMaker

#   компонуется сам

vbar = Scrollbar(self)

hbar = Scrollbar(self, orient=’horizontal’)

text = Text(self, padx=5, wrap=’none’) # запретить перенос строк text.config(undo=1, autoseparators=1) # 2.0, по умолчанию 0, 1

vbar.pack(side=RIGHT, fill=Y)

hbar.pack(side=BOTTOM, fill=X) # скомпоновать Text последним

text.pack(side=TOP, fill=BOTH, expand=YES) # иначе обрежутся полосы

# прокрутки

text.config(yscrollcommand=vbar.set) # вызывать vbar.set при

text.config(xscrollcommand=hbar.set) # перемещении по тексту

vbar.config(command=text.yview) # вызывать text.yview при прокрутке hbar.config(command=text.xview) # или hbar[‘command’]=text.xview

# 2.0: применить пользовательские настройки или умолчания

startfont = configs.get(‘font’, self.fonts[0])

startbg = configs.get(‘bg’, self.colors[0][‘bg’])

startfg = configs.get(‘fg’, self.colors[0][‘fg’]) text.config(font=startfont, bg=startbg, fg=startfg) if ‘height’ in configs: text.config(height=configs[‘height’]) if ‘width’ in configs: text.config(width =configs[‘width’]) self.text = text

self.filelabel = name

########################################################################## # Операции меню File

##########################################################################

def my_askopenfilename(self): # объекты запоминают каталог/файл

if not self.openDialog: # последней операции

self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes)

return self.openDialog.show()

def my_asksaveasfilename(self): # объекты запоминают каталог/файл

if not self.saveDialog: # последней операции

self.saveDialog = SaveAs(initialdir=self.startfiledir, filetypes=self.ftypes)

return self.saveDialog.show()

def onOpen(self, loadFirst=’’, loadEncode=’’):

2.1: полностью переписан для поддержки Юникода; открывает в текстовом режиме с кодировкой, переданной в аргументе, введенной пользователем, заданной в модуле textconfig или с кодировкой по умолчанию;

в крайнем случае открывает файл в двоичном режиме и отбрасывает символы \r в Windows, если они присутствуют, чтобы обеспечить нормальное отображение текста; содержимое извлекается и возвращается в виде строки str, поэтому при сохранении его требуется кодировать: сохраняет кодировку, используемую здесь;

предварительно проверяет возможность открытия файла;

мы могли бы также вручную загружать и декодировать bytes в str, чтобы избежать необходимости выполнять несколько попыток открытия, но этот прием подходит не для всех случаев;

порядок выбора кодировки настраивается в локальном textConfig.py:

1) сначала применяется кодировка, переданная клиентом (например, кодировка из заголовка сообщения электронной почты)

2) затем, если opensAskUser возвращает True, применяется кодировка, введенная пользователем (предварительно в диалог записывается кодировка по умолчанию)

3) затем, если opensEncoding содержит непустую строку, применяется эта кодировка: latin-1’ и так далее.

4) затем выполняется попытка применить кодировку sys.getdefaultencoding()

5) в крайнем случае выполняется чтение в двоичном режиме и используется алгоритм, заложенный в библиотеку Tk

if self.text_edit_modified(): # 2.0

if not askyesno(‘PyEdit’, ‘Text has changed: discard changes?’): return

file = loadFirst or self.my_askopenfilename() if not file:

return

if not os.path.isfile(file):

showerror(‘PyEdit’, ‘Could not open file ‘ + file) return

#   применить известную кодировку, если указана

#   (например, из заголовка сообщения электронной почты)

text = None # пустой файл = ‘’ = False: проверка на None!

if loadEncode:

try:

text = open(file, ‘r’, encoding=loadEncode).read() self.knownEncoding = loadEncode

except (UnicodeError, LookupError, IOError): # Lookup: ошибка pass # в имени

#   применить кодировку, введенную пользователем,

#   предварительно записать в диалог следующий вариант, как значение

#   по умолчанию

if text == None and self.opensAskUser:

self.update() # иначе в некоторых случаях диалог не появится askuser = askstring(‘PyEdit’, ‘Enter Unicode encoding for open’, initialvalue=(self.opensEncoding or

sys.getdefaultencoding() or ‘’)) if askuser:

try:

text = open(file, ‘r’, encoding=askuser).read() self.knownEncoding = askuser

except (UnicodeError, LookupError, IOError): pass

#   применить кодировку из файла с настройками (может быть, выполнять

#   эту попытку до того, как запрашивать кодировку у пользователя?)

if text == None and self.opensEncoding:

try:

text = open(file, ‘r’, encoding=self.opensEncoding).read() self.knownEncoding = self.opensEncoding

except (UnicodeError, LookupError, IOError): pass

#  применить системную кодировку по умолчанию (utf-8 в windows;

#  всегда пытаться использовать utf8?) if text == None:

try:

text = open(file, ‘r’,

encoding=sys.getdefaultencoding()).read() self.knownEncoding = sys.getdefaultencoding()

except (UnicodeError, LookupError, IOError): pass

#  крайний случай: использовать двоичный режим и положиться на # возможности Tk if text == None:

try:

text = open(file, ‘rb’).read() # строка bytes text = text.replace(b’\r\n’, b’\n’) # для отображения self.knownEncoding = None # и последующего сохранения

except IOError:

pass

if text == None:

showerror(‘PyEdit’, ‘Could not decode and open file ‘ + file) else:

self.setAllText(text)

self.setFileName(file)

self.text.edit_reset() # 2.0: очистка стеков undo/redo

self.text.edit_modified(0) # 2.0: сбросить флаг наличия изменений

def onSave(self):

self.onSaveAs(self.currfile) # may be None

def onSaveAs(self, forcefile=None):

2.1: полностью переписан для поддержки Юникода: виджет Text всегда возвращает содержимое в виде строки str, поэтому нам необходимо побеспокоиться о кодировке, чтобы сохранить файл, независимо от режима, в котором открывается выходной файл (для двоичного режима необходимо будет получить bytes, а для текстового необходимо указать кодировку); пытается применить кодировку, использовавшуюся при открытии или сохранении (если известна), предлагаемую пользователем, указанную в файле с настройками, и системную кодировку по умолчанию; в большинстве случаев можно использовать системную кодировку по умолчанию;

в случае успешного выполнения операции сохраняет кодировку для использования в дальнейшем, потому что это может быть первая операция Save после операции New или вставки текста вручную; в файле с настройками можно определить, чтобы обе операции, Save и Save As, использовали последнюю известную кодировку (однако если для операции Save это оправданно, то в случае с операцией Save As это не так очевидно); графический интерфейс предварительно записывает эту кодировку в диалог, если она известна;

выполняет text.encode() вручную, чтобы избежать создания файла; для текстовых файлов автоматически выполняется преобразование символов конца строки: в Windows добавляются символы \r, отброшенные при открытии файла в текстовом (автоматически) или в двоичном (вручную) режиме; Если содержимое вставлялось вручную, здесь необходимо предварительно удалить символы \r, иначе они будут продублированы; knownEncoding=None перед первой операцией Open или Save, после New и если операция Open открыла файл в двоичном режиме;

порядок выбора кодировки настраивается в локальном textConfig.py:

1) если savesUseKnownEncoding > 0, применить кодировку, использованную в последней операции Open или Save

2) если savesAskUser = True, применить кодировку, указанную пользователем (предлагать известную в качестве значения по умолчанию?)

3) если savesEncoding — непустая строка, применить эту кодировку: utf-8’ и так далее

4)  в крайнем случае применить sys.getdefaultencoding()

filename = forcefile or self.my_asksaveasfilename() if not filename:

return

text = self.getAllText() # 2.1: строка str, без символов \r, encpick = None # даже если текст читался/вставлялся

# в двоичном виде

#  применить известную кодировку, использовавшуюся в последней операции # Open или Save, если известна

if self.knownEncoding and ( # известна?

(forcefile and self.savesUseKnownEncoding >= 1) or # для Save? (not forcefile and self.savesUseKnownEncoding >= 2)):# для SaveAs? try:

text.encode(self.knownEncoding)

encpick = self.knownEncoding

except UnicodeError: pass

#  применить кодировку, введенную пользователем,

#  предварительно записать в диалог следующий вариант, как значение # по умолчанию

if not encpick and self.savesAskUser:

self.update()# иначе в некоторых случаях диалог не появится askuser = askstring(‘PyEdit’, ‘Enter Unicode encoding for save’, initialvalue=(self.knownEncoding or self.savesEncoding or sys.getdefaultencoding() or ‘’)) if askuser:

try:

text.encode(askuser)

encpick = askuser

except (UnicodeError, LookupError): # LookupError: ошибка в имени pass # UnicodeError: ошибка

# кодирования

#   применить кодировку из файла с настройками

if not encpick and self.savesEncoding:

try:

text.encode(self.savesEncoding)

encpick = self.savesEncoding

except (UnicodeError, LookupError): pass

#   применить системную кодировку по умолчанию (utf8 в windows)

if not encpick:

try:

text.encode(sys.getdefaultencoding())

encpick = sys.getdefaultencoding()

except (UnicodeError, LookupError): pass

#   открыть в текстовом режиме, чтобы автоматически выполнить

#   преобразование символов конца строки и применить кодировку

if not encpick:

showerror(‘PyEdit’, ‘Could not encode for file ‘ + filename)

else:

try:

file = open(filename, ‘w’, encoding=encpick)

file.write(text)

file.close()

except:

showerror(‘PyEdit’, ‘Could not write file ‘ + filename)

else:

self.setFileName(filename) # может быть вновь созданным

self.text.edit_modified(0) # 2.0: сбросить флаг изменений

self.knownEncoding = encpick # 2.1: запомнить кодировку # не сбрасывать стеки undo/redo!

def onNew(self):

запускает редактирование совершенно нового файла в текущем окне;

смотрите метод onClone, который вместо этого создает независимое окно редактирования;

if self.text_edit_modified(): # 2.0

if not askyesno(‘PyEdit’, ‘Text has changed: discard changes?’): return

self.setFileName(None)

self.clearAllText()

self.text.edit_reset() # 2.0: очистить стеки undo/redo

self.text.edit_modified(0) # 2.0: сбросить флаг наличия изменений

self.knownEncoding = None # 2.1: кодировка неизвестна

def onQuit(self):

вызывается выбором операции Quit в меню/панели инструментов и щелчком на кнопке X в заголовке окна;

2.1:  не завершать приложение при наличии несохраненных изменений;

2.2:  не выводить запрос на подтверждение, если нет изменений в self; перемещен в классы окон верхнего уровня ниже, так как его

реализация может зависеть от особенностей использования: операция Quit в графическом интерфейсе может вызывать метод quit() для завершения, destroy() — чтобы просто закрыть окно Toplevel, Tk или фрейм с редактором, эта операция может даже вообще не предоставляться, если редактор присоединяется, как компонент; проверяет self на наличие несохраненных изменений, а если предполагается вызов метода quit(), главные окна должны также проверить наличие несохраненных изменений в других окнах, присутствующих в глобальном списке процесса;

assert False, ‘onQuit must be defined in window-specific sublass’

def text_edit_modified(self):

2.3: теперь действует! кажется, проблема заключалась в типе bool результата в tkinter;

2.0: self.text.edit_modified() не работает в Python 2.4: выполнить проверку вручную;

return self.text.edit_modified()

#return self.tk.call((self.text._w, ‘edit’) + (‘modified’, None))

########################################################################## # Операции меню Edit

##########################################################################

def onUndo(self): # 2.0

try: # tk8.4 поддерживает стеки undo/redo

self.text.edit_undo() # возбуждает исключение, если стеки пустые except TclError: # меню открывается для быстрого доступа

showinfo(‘PyEdit’, ‘Nothing to undo’) # к операциям

def onRedo(self): # 2.0: возврат отмененной операции

try: # редактирования

self.text.edit_redo()

except TclError:

showinfo(‘PyEdit’, ‘Nothing to redo’)

def onCopy(self): # получить текст, выделенный мышью

if not self.text.tag_ranges(SEL): # сохранить в системном буфере

showerror(‘PyEdit’, ‘No text selected’) else:

text = self.text.get(SEL_FIRST, SEL_LAST)

self.clipboard_clear()

self.clipboard_append(text)

def onDelete(self): # удалить выделенный текст без сохранения

if not self.text.tag_ranges(SEL):

showerror(‘PyEdit’, ‘No text selected’) else:

self.text.delete(SEL_FIRST, SEL_LAST)

def onCut(self):

if not self.text.tag_ranges(SEL):

showerror(‘PyEdit’, ‘No text selected’) else:

self.onCopy() # сохранить и удалить выделенный текст

self.onDelete()

def onPaste(self):

try:

text = self.selection_get(selection=’CLIPBOARD’)

except TclError:

showerror(‘PyEdit’, ‘Nothing to paste’) return

self.text.insert(INSERT, text) # вставить в текущую позицию курсора self.text.tag_remove(SEL, ‘1.0’, END)

self.text.tag_add(SEL, INSERT+’-%dc’ % len(text), INSERT)

self.text.see(INSERT) # выделить, чтобы можно было вырезать

def onSelectAll(self):

self.text.tag_add(SEL, ‘1.0’, END+’-1c’)# выделить весь текст

self.text.mark_set(INSERT, ‘1.0’) # переместить позицию в начало

self.text.see(INSERT) # прокрутить в начало

########################################################################## # Операции меню Search

##########################################################################

def onGoto(self, forceline=None):

line = forceline or askinteger(‘PyEdit’, ‘Enter line number’) self.text.update() self.text.focus()

if line is not None:

maxindex = self.text.index(END+’-1c’)

maxline = int(maxindex.split(‘.’)[0])

if line > 0 and line <= maxline:

self.text.mark_set(INSERT, ‘%d.0’ % line) # перейти к стр.

self.text.tag_remove(SEL, ‘1.0’, END) # снять выделен.

self.text.tag_add(SEL, INSERT, ‘insert + 1l’) # выделить стр.

self.text.see(INSERT) # прокрутить

else: # до строки

showerror(‘PyEdit’, ‘Bad line number’)

def onFind(self, lastkey=None):

key = lastkey or askstring(‘PyEdit’, ‘Enter search string’) self.text.update()

self.text.focus() self.lastfind = key if key: # 2.0: без учета регистра символов

nocase = configs.get(‘caseinsens’, True) # 2.0: настройки where = self.text.search(key, INSERT, END, nocase=nocase) if not where: # не переходить

showerror(‘PyEdit’, ‘String not found’)# в начало else:

pastkey = where + ‘+%dc’ % len(key) # позиция после ключа self.text.tag_remove(SEL, ‘1.0’, END) # снять выделение self.text.tag_add(SEL, where, pastkey) # выделить ключ self.text.mark_set(INSERT, pastkey) # для след. поиска self.text.see(where) # прокрутить экран

def onRefind(self):

self.onFind(self.lastfind)

def onChange(self):

немодальный диалог поиска с заменой

2.1:  поля ввода диалога передаются обработчику, допускается открывать одновременно несколько диалогов поиска с заменой

new = Toplevel(self)

new.title(‘PyEdit change’)

Label(new, text=’Find text?’, relief=RIDGE, width=15).grid(row=0,

column=0)

Label(new, text=’Change to?’, relief=RIDGE, width=15).grid(row=1, column=0) entry1 = Entry(new) entry2 = Entry(new) entry1.grid(row=0, column=1, sticky=EW) entry2.grid(row=1, column=1, sticky=EW)

def onFind(): # использует поле ввода из внешней обл. видимости

self.onFind(entry1.get()) # вызов обработчика диалога поиска

def onApply():

self.onDoChange(entry1.get(), entry2.get())

Button(new, text=’Find’, command=onFind ).grid(row=0,

column=2, sticky=EW) Button(new, text=’Apply’, command=onApply).grid(row=1,

column=2, sticky=EW) new.columnconfigure(1, weight=1) # растягиваемые поля ввода

def onDoChange(self, findtext, changeto):

#  реализует замену для диалога поиска с заменой:

#  заменяет и повторяет поиск

if self.text.tag_ranges(SEL): # сначала найти

self.text.delete(SEL_FIRST, SEL_LAST)

self.text.insert(INSERT, changeto) # удалит, если пусто self.text.see(INSERT)

self.onFind(findtext) # переход к следующему

self.text.update() # принудительное обновление

def onGrep(self):

новое в версии 2.1: многопоточная реализация поиска во внешних файлах; выполняет поиск указанной строки в файлах, имена которых соответствуют заданному шаблону; щелчок на элементе в списке открывает соответствующий файл, при этом выполняется переход к строке с найденным вхождением;

поиск выполняется в отдельном потоке, чтобы графический интерфейс не блокировался и оставался активным и чтобы позволить одновременно выполнять несколько операций поиска; можно было бы использовать модуль, если прекращать цикл проверки при отсутствии активных операций поиска;

алгоритм выбора кодировки при выполнении поиска: содержимое текстовых файлов в дереве, где выполняется поиск, может храниться в любых кодировках: мы не предлагаем вводить имя кодировки для каждого файла (как при открытии), однако позволяем указать кодировку для всего дерева, предварительно устанавливая общесистемную кодировку по умолчанию, используемую файловой системой или для представления текста, и пропускаем файлы, декодирование которых терпит неудачу; в самом тяжелом случае пользователю может потребоваться выполнить поиск N раз, если в дереве могут присутствовать файлы с текстом в N различных кодировках; иначе операция открытия будет возбуждать исключение, а открытие в двоичном режиме может не дать совпадения кодированного текста с испытуемой строкой;

TBD: может, лучше было бы выводить сообщение об ошибке при встрече с файлом, который не удалось декодировать?

но файлы с кодировкой utf-16 (2 байта на символ), созданные в Notepad, благополучно могут декодироваться с применением кодировки utf-8, однако строка при этом не будет найдена;

TBD: можно было бы позволить вводить несколько имен кодировок, отделяя их друг от друга запятыми, и пробовать применять их поочередно к каждому файлу, помимо loadEncode

from PP4E.Gui.ShellGui.formrows import makeFormRow

#  немодальный диалог: ввод имени каталога, шаблон имени файла, # искомая строка popup = Toplevel()

popup.title(‘PyEdit grep’)

var1 = makeFormRow(popup, label=’Directory root’, width=18, browse=False)

var2 = makeFormRow(popup, label=’Filename pattern’, width=18, browse=False)

var3 = makeFormRow(popup, label=’Search string’, width=18, browse=False)

var4 = makeFormRow(popup, label=’Content encoding’, width=18, browse=False)

var1.set(‘.’) # текущий каталог

var2.set(‘*.py’) # начальные значения

var4.set(sys.getdefaultencoding()) # для содержимого файлов, а не имен cb = lambda: self.onDoGrep(var1.get(), var2.get(), var3.get(), var4.get())

Button(popup, text=’Go’,command=cb).pack()

def onDoGrep(self, dirname, filenamepatt, grepkey, encoding):

вызывается щелчком на кнопке Go в диалоге Grep: заполняет список найденными совпадениями

tbd: возможно, следует запускать поток-производитель как демон, чтобы он автоматически завершался вместе с приложением?

import threading, queue

#  создать немодальный и незакрываемый диалог mypopup = Tk()

mypopup.title(‘PyEdit grepping’)

status = Label(mypopup,

text=’Grep thread searching for: %r…’ % grepkey) status.pack(padx=20, pady=20)

mypopup.protocol(‘WM_DELETE_WINDOW’, lambda: None) # игнорировать

# кнопку X

#  запустить потокпроизводитель, цикл проверки результатов myqueue = queue.Queue() threadargs = (filenamepatt, dirname, grepkey, encoding, myqueue) threading.Thread(target=self.grepThreadProducer, args=threadargs).start()

self.grepThreadConsumer(grepkey, encoding, myqueue, mypopup)

def grepThreadProducer(self, filenamepatt, dirname, grepkey, encoding, myqueue):

выполняется в параллельном потоке, не имеющем отношения к графическому интерфейсу: помещает в очередь список с результатами find.find; найденные совпадения можно было бы помещать в очередь по мере их обнаружения, но для этого необходимо обеспечить сохранение окна на экране; здесь могут возникать ошибки декодирования не только содержимого, но и имен файлов;

TBD: чтобы избежать ошибок декодирования имен файлов в os.walk/listdir, можно было бы передавать методу find() строку bytes, но какую кодировку использовать: sys.getfilesystemencoding(), если она не равна None? Смотрите также примечание в разделе “Модуль fnmatchв главе 6: в версии 3.1 модуль fnmatch всегда преобразует текст в двоичное представление, используя кодировку Latin-1;

from PP4E.Tools.find import find

matches = []

try:

for filepath in find(pattern=filenamepatt, startdir=dirname): try:

textfile = open(filepath, encoding=encoding)

for (linenum, linestr) in enumerate(textfile): if grepkey in linestr:

msg = ‘%s@%d [%s]’ % (filepath,

linenum + 1, linestr) matches.append(msg)

except UnicodeError as X: # напр.: декодир.,

print(‘Unicode error in:’, filepath, X) # двоичный режим

except IOError as X:

print(‘IO error in:’, filepath, X) # напр.: права доступа finally:

myqueue.put(matches) # остановить цикл потребителя при исключении: # имена файлов?

def grepThreadConsumer(self, grepkey, encoding, myqueue, mypopup):

выполняется в главном потоке графического интерфейса: просматривает очередь в ожидании результатов или []; может иметься несколько активных потоков/циклов/очередей, связанных с поиском; в процессе могут присутствовать другие типы потоков/циклов проверки, особенно если PyEdit прикрепляется как компонент (PyMailGUI);

import queue

try:

matches = myqueue.get(block=False)

except queue.Empty:

myargs = (grepkey, encoding, myqueue, mypopup)

self.after(250, self.grepThreadConsumer, *myargs) else:

mypopup.destroy() # закрыть информационный диалог self.update() # и стереть его с экрана if not matches:

showinfo(‘PyEdit’, ‘Grep found no matches for: %r’ % grepkey) else:

self.grepMatchesList(matches, grepkey, encoding)

def grepMatchesList(self, matches, grepkey, encoding):

заполняет список найденными совпадениями в случае успеха;

так как поиск увенчался успехом, кодировка уже известна: использовать ее в обработчике щелчка на файле в списке, чтобы обеспечить его открытие без обращения к пользователю;

from PP4E.Gui.Tour.scrolledlist import ScrolledList print(‘Matches for %s: %s’ % (grepkey, len(matches)))

#   перехватывает двойной щелчок на списке

class ScrolledFilenames(ScrolledList):

def runCommand(self, selection):

file, line = selection.split(‘ [‘, 1)[0].split(‘@’)

editor = TextEditorMainPopup(

loadFirst=file, winTitle=’ grep match’,

loadEncode=encoding)

editor.onGoto(int(line))

editor.text.focus_force() # на самом деле не требуется

#   новое модальное окно

popup = Tk()

popup.title(‘PyEdit grep matches: %r (%s)’ % (grepkey, encoding))

ScrolledFilenames(parent=popup, options=matches) ########################################################################## # Операции меню Tools

########################################################################## def onFontList(self):

self.fonts.append(self.fonts[0]) # выбрать следующий шрифт в списке

del self.fonts[0] # изменит размер текстовой области

self.text.config(font=self.fonts[0]) def onColorList(self):

self.colors.append(self.colors[0]) # выбрать следующий цвет в списке

del self.colors[0] # текущий сместить в конец

self.text.config(fg=self.colors[0][‘fg’], bg=self.colors[0][‘bg’])

def onPickFg(self):

self.pickColor(‘fg’) # добавлено 10/02/00

def onPickBg(self): # выбрать произвольный цвет

self.pickColor(‘bg’) # в стандартном диалоге выбора цвета

def pickColor(self, part): # это очень просто

(triple, hexstr) = askcolor() if hexstr:

self.text.config(**{part: hexstr})

def onInfo(self):

диалог с информацией о тексте и о местоположении курсора;

ВНИМАНИЕ (2.1): при вычислении позиции курсора библиотека Tk считает символ табуляции, как один символ: следует умножать их на 8, чтобы обеспечить соответствие с визуальным положением?

text = self.getAllText() # добавлено 5/3/00 за 15 мин.

bytes = len(text) # словами считается все, что

lines = len(text.split(‘\n’)) # отделяется пробелами

words = len(text.split()) # 3.x: в bytes символы

index = self.text.index(INSERT) # в str кодовые пункты Юникода

where = tuple(index.split(‘.’)) showinfo(‘PyEdit Information’,

‘Current location:\n\n’ + ‘line:\t%s\ncolumn:\t%s\n\n’ % where + ‘File text statistics:\n\n’ + ‘chars:\t%d\nlines:\t%d\nwords:\t%d\n’ % (bytes, lines, words))

def onClone(self, makewindow=True):

открывает новое окно редактора, не изменяя уже открытое (onNew); наследует поведение операции Quit и других от окна, копия которого создается;

2.1: подклассы должны переопределять/замещать этот метод, если будут создавать собственные окна,

иначе этот метод создаст дополнительное поддельное пустое окно;

if not makewindow:

new = None # предполагается, что класс создает

else: # собственное окно

new = Toplevel() # новое окно редактора в том же процессе myclass = self.__class__ # объект класса экземпляра (самый нижний) myclass(new) # прикрепить/запустить экземпляр моего класса

def onRunCode(self, parallelmode=True):

выполнение редактируемого программного кода Python — это не IDE, но удобно; пытается выполнить в каталоге файла, не в cwd (может быть корнем PP4E); вводит и добавляет аргументы командной строки для файлов сценариев;

stdin/out/err для программного кода = стартовое окно редактора, если оно есть: запускайте редактор в окне консоли, чтобы увидеть вывод, производимый программным кодом; если parallelmode=True, открывает окно DOS для операций ввода-вывода; путь поиска модулей будет включать ‘.’ при запуске; при выполнении программного кода как отдельной строки корневым окном может быть окно PyEdit; здесь также можно использовать модули subprocess и multiprocessing;

2.1: исправлено на использование базового имени файла после chdir, без пути;

2.1: использует StartArgs для передачи аргументов в режиме запуска файлов в Windows;

2.1: вызывает update() после первого диалога, в противном случае второй диалог иногда не появляется на экране;

def askcmdargs():

return askstring(‘PyEdit’, ‘Commandline arguments?’) or ‘’

from PP4E.launchmodes import System, Start, StartArgs, Fork filemode = False

thefile = str(self.getFileName())

if os.path.exists(thefile):

filemode = askyesno(‘PyEdit’, ‘Run from file?’)

self.update() # 2.1: вызывает update()

if not filemode: # выполнить как строку

cmdargs = askcmdargs()

namespace = {‘__name__’: ‘__main__’} # выполнить как сценарий

sys.argv = [thefile] + cmdargs.split() # можно использов. потоки exec(self.getAllText() + ‘\n’, namespace)# игнорировать исключения

elif self.text_edit_modified(): # 2.0: проверка изменений

showerror(‘PyEdit’, ‘Text changed: you must save before run’)

else:

cmdargs = askcmdargs()

mycwd = os.getcwd() # cwd может быть корнем

dirname, filename = os.path.split(thefile) # каталог, базовое имя

подпись: # cd для файлов
# 2.1: не thefile
# выполнить как файл
# блокировать редактор
# породить параллельно
os.chdir(dirname or mycwd)

thecmd = filename + ‘ ‘ + cmdargs if not parallelmode:

System(thecmd, thecmd)()

else:

if sys.platform[:3] == ‘win’:

run = StartArgs if cmdargs else Start # 2.1: аргументы

run(thecmd, thecmd)() # или всегда Spawn

else:

Fork(thecmd, thecmd)() # породить параллельно

os.chdir(mycwd) # вернуться в каталог def onPickFont(self):

2.2: немодальный диалог выбора шрифта

2.3:  поля ввода диалога передаются обработчику, допускается открывать одновременно несколько диалогов поиска выбора шрифта

from PP4E.Gui.ShellGui.formrows import makeFormRow

popup = Toplevel(self)

popup.title(‘PyEdit font’)

var1 = makeFormRow(popup, label=’Family’, browse=False)

var2 = makeFormRow(popup, label=’Size’, browse=False)

var3 = makeFormRow(popup, label=’Style’, browse=False)

var1.set(‘courier’)

var2.set(‘12’) # предлагаемые значения

var3.set(‘bold italic’) # смотрите допустимые значения в списке выбора

Button(popup, text=’Apply’, command=

lambda: self.onDoFont(var1.get(), var2.get(),

var3.get())).pack()

def onDoFont(self, family, size, style):

try:

self.text.config(font=(family, int(size), style)) except:

showerror(‘PyEdit’, ‘Bad font specification’)

########################################################################## # Прочие утилиты, полезные за пределами этого класса

##########################################################################

def isEmpty(self):

return not self.getAllText()

def getAllText(self):

return self.text.get(‘1.0’, END+’-1c’) # извлечь текст как строку str

def setAllText(self, text):

вызывающий: должен предварительно вызвать self.update(), если только что был прикреплен, иначе начальная позиция может оказаться не в первой, а во второй строке (2.1; ошибка Tk?)

self.text.delete(‘1.0’, END) # записать текстовую строку в виджет

self.text.insert(END, text) # или ‘1.0’; текст = bytes или str

self.text.mark_set(INSERT, ‘1.0’) # переместить точку ввода в начало self.text.see(INSERT) # прокрутить в начало, в точку вставки

def clearAllText(self):

self.text.delete(‘1.0’, END) # очистить текст в виджете

def getFileName(self): return self.currfile

def setFileName(self, name): # смотрите также: onGoto(linenum) self.currfile = name # для последующего сохранения self.filelabel.config(text=str(name))

def setKnownEncoding(self, encoding=’utf-8’): # 2.1: для сохранения self.knownEncoding = encoding # иначе будут использованы настройки, # запрос?

def setBg(self, color):

self.text.config(bg=color) # для установки вручную из программы def setFg(self, color):

self.text.config(fg=color) # ‘black’, шестнадцатеричная строка

def setFont(self, font):

self.text.config(font=font) # (‘семейство’, размер, ‘стиль’)

def setHeight(self, lines): # по умолчанию = 24 строки x 80 символов

self.text.config(height=lines)# можно также взять из textCongif.py

def setWidth(self, chars):

self.text.config(width=chars)

def clearModified(self):

self.text.edit_modified(0) # сбросить флаг наличия изменений def isModified(self): # были изменения с момента

return self.text_edit_modified() # последнего сброса флага?

def help(self):

showinfo(‘About PyEdit’, helptext % ((Version,)*2))

##############################################################################

#   Готовые к употреблению классы редактора, подмешиваемые в подкласс

#   фрейма GuiMaker, создающий меню и панели инструментов.

#

#   Эти классы реализуют типичные случаи использования, однако возможны и другие

#   реализации; для запуска PyEdit, как самостоятельной программы, следует

#   вызвать метод TextEditorMain().mainloop(); переопределяйте/расширяйте

#   в подклассах метод onQuit, чтобы обеспечить перехват события завершения

#   приложения или уничтожения окна (смотрите пример PyView);

#   ВНИМАНИЕ: можно было бы использовать windows.py для создания ярлыков,

#   но здесь используется собственный протокол завершения.

##############################################################################

#  

#   2.1: в quit(), не завершать без предупреждения, если в процессе открыты

#   другие окна редактора и в них имеются несохраненные изменения — изменения

#   будут потеряны, потому что все остальные окна тоже закрываются, включая

#   множественные родительские окна Tk, включающие редактор; для слежения за

#   всеми окнами PyEdit используется список экземпляров, созданных в процессе;

#   это может оказаться чрезмерной мерой (если вместо quit() вызывается

#   destroy(), когда достаточно проверить только дочернее окно редактирования

#   уничтожаемого родителя), но лучше перестраховаться; метод onQuit перемещен

#   сюда, потому что его реализация отличается для окон разных типов и может

#   присутствовать не во всех окнах;

#

#   предполагается, что TextEditorMainPopup никогда не будет играть роль

#   родителя для других окон редактирования — дочерние виджеты Toplevel

#   уничтожаются вместе со своими родителями; это не позволяет предотвратить

#   закрытие из-за пределов классов PyEdit (метод quit в tkinter доступен

#   во всех виджетах, и любой виджет может быть родителем для Toplevel!);

#   ответственность за проверку наличия изменений в содержимом редактора

#   полностью возлагается на клиента; обратите внимание, что в данной ситуации

#   привязка события <Destroy> не даст ровным счетом ничего, потому что его

#   обработчик не может выполнять операции с графическим интерфейсом, такие как

#   проверка наличия изменений и извлечение текста, — дополнительную информацию

#   об этом событии смотрите в книге и в модуле destroyer.py;

#    ########################################

#   когда текстовый редактор владеет окном

########################################

class TextEditorMain(TextEditor, GuiMakerWindowMenu):

главное окно редактора PyEdit, которое вызывает метод quit() при выполнении операции Quit графического интерфейса для завершения приложения и конструирует меню в окне; родителем может быть окно Tk, по умолчанию, окно Tk, создаваемое явно, или объект Toplevel:

родитель должен быть окном и, вероятно, окном Tk, чтобы избежать закрытия без предупреждения вместе с родителем; при выполнении операции Quit графического интерфейса все главные окна PyEdit проверяют остальные окна

PyEdit, открытые в процессе, на наличие несохраненных изменений, поскольку вызов метода quit() здесь приведет к завершению всего приложения; фрейм редактора необязательно должен занимать окно целиком (окно может включать и другие компоненты: смотрите PyView), но его операция Quit завершает программу; метод onQuit вызывается операцией Quit, выполняемой щелчком на кнопке в панели инструментов, выбором пункта в меню File, а также щелчком на кнопке X в заголовке окна;

def __init__(self, parent=None, loadFirst=’’, loadEncode=’’):

# редактор занимает все родительское окно

GuiMaker.__init__(self, parent) # использует главное меню окна

TextEditor.__init__(self, loadFirst, loadEncode)# фрейм GuiMaker

# прикрепляет себя сам self.master.title(‘PyEdit ‘ + Version) # заголовок, кнопка X, если self.master.iconname(‘PyEdit’) # выполняется как отдельная

self.master.protocol(‘WM_DELETE_WINDOW’, self.onQuit) # программа TextEditor.editwindows.append(self)

def onQuit(self): # вызывается операцией Quit

close = not self.text_edit_modified() # проверить себя, запросить, if not close: # проверить другие

close = askyesno(‘PyEdit’,

‘Text changed: quit and discard changes?’) if close:

windows = TextEditor.editwindows

changed = [w for w in windows

if w != self and w.text_edit_modified()]

if not changed:

GuiMaker.quit(self) # завершить все приложение, независимо от else: # типа виджета

numchange = len(changed) verify = ‘%s other edit window%s changed: verify = verify + ‘quit and discard anyhow?’ verify = verify % (numchange, ‘s’ if numchange > 1 else ‘’) if askyesno(‘PyEdit’, verify):

GuiMaker.quit(self)

class TextEditorMainPopup(TextEditor, GuiMakerWindowMenu):

всплывающее окно PyEdit, которое вызывает метод destroy() при выполнении операции Quit графического интерфейса, закрывает только себя и создает меню в окне; создает собственного родителя Toplevel, который является дочерним для окна Tk по умолчанию (если передается значение None) или для другого указанного окна или виджета (например, для фрейма);

добавляется в список для проверки при закрытии любого главного окна PyEdit; если будет создано главное окно PyEdit, родитель данного окна также должен быть родителем главного окна PyEdit, чтобы оно не было закрыто без предупреждения; метод onQuit вызывается операцией Quit, выполняемой щелчком на кнопке в панели инструментов, выбором пункта в меню File, а также щелчком на кнопке X в заголовке окна;

def __init__(self, parent=None, loadFirst=’’, winTitle=’’, loadEncode=’’): # создать собственное окно

self.popup = Toplevel(parent)

GuiMaker.__init__(self, self.popup) # использует главное меню окна TextEditor.__init__(self, loadFirst, loadEncode) # фрейм в новом окне assert self.master == self.popup self.popup.title(‘PyEdit ‘ + Version + winTitle) self.popup.iconname(‘PyEdit’) self.popup.protocol(‘WM_DELETE_WINDOW’, self.onQuit) TextEditor.editwindows.append(self)

def onQuit(self): close = not self.text_edit_modified() if not close:

close = askyesno(‘PyEdit’,

‘Text changed: quit and discard changes?’) if close:

self.popup.destroy() # закрыть только это окно

TextEditor.editwindows.remove(self) # (и все дочерние окна)

def onClone(self):

TextEditor.onClone(self, makewindow=False) # я создаю собственное окно

########################################### # когда редактор встраивается в другое окно ###########################################

class TextEditorComponent(TextEditor, GuiMakerFrameMenu):

прикрепляемый фрейм компонента PyEdit с полными меню/панелью инструментов, который вызывает destroy() при выполнении операции Quit графического интерфейса и стирает только себя; при выполнении операции Quit проверяется наличие несохраненных изменений только в этом редакторе; не перехватывает щелчок на кнопке X в заголовке окна: не имеет собственного окна; не добавляет себя в список отслеживаемых окон: является частью более крупного приложения;

def __init__(self, parent=None, loadFirst=’’, loadEncode=’’):

# использовать меню на основе фрейма

GuiMaker.__init__(self, parent) # все меню, кнопки в GuiMaker должны TextEditor.__init__(self, loadFirst, loadEncode) # создаваться первыми

def onQuit(self):

close = not self.text_edit_modified() if not close:

close = askyesno(‘PyEdit’,

‘Text changed: quit and discard changes?’) if close:

self.destroy() # стереть свой фрейм, но не завершать вмещающее # приложение

class TextEditorComponentMinimal(TextEditor, GuiMakerFrameMenu):


прикрепляемый фрейм компонента PyEdit без операции Quit и без меню File;

на запуске удаляет кнопку Quit из панели инструментов и удаляет меню File или запрещает все его пункты (грубовато, зато эффективно);

структуры меню и панели инструментов являются данными экземпляра:

изменение их не затрагивает другие экземпляры;

Операция Quit графического интерфейса никогда не запускается, потому что она удаляется из доступных операций;

def __init__(self, parent=None, loadFirst=’’, deleteFile=True, loadEncode=’’):

self.deleteFile = deleteFile

GuiMaker.__init__(self, parent) # фрейм GuiMaker прикрепляет себя сам TextEditor.__init__(self, loadFirst, loadEncode) # TextEditor

#   добавляется

#   в середину

def start(self):

TextEditor.start(self) # вызов метода start GuiMaker

for i in range(len(self.toolBar)): # удалить quit из панели инстр.

if self.toolBar[i][0] == ‘Quit’: # удалить пункты меню file del self.toolBar[i] # или просто запретить их

break

if self.deleteFile:

for i in range(len(self.menuBar)):

if self.menuBar[i][0] == ‘File’:

del self.menuBar[i] break else:

for (name, key, items) in self.menuBar:

if name == ‘File’:

items.append([1,2,3,4,6])

############################################################################## # запуск как самостоятельной программы

##############################################################################

def testPopup():

# проверку запуска как компонента смотрите в PyView и PyMail

root = Tk()

TextEditorMainPopup(root)

TextEditorMainPopup(root)

Button(root, text=’More’, command=TextEditorMainPopup).pack(fill=X)

Button(root, text=’Quit’, command=root.quit).pack(fill=X) root.mainloop()

def main(): # из командной строки или щелчком

try: # либо как ассоциированная программа в Windows

fname = sys.argv[1] # аргумент = необязательное имя файла

except IndexError: # создается в корневом окне Tk по умолчанию

fname = None

TextEditorMain(loadFirst=fname).pack(expand=YES, fill=BOTH)# pack — mainloop() # необязательно

if __name__ == ‘__main__’: # когда запускается как сценарий

#testPopup()

main() # используйте .pyw, чтобы запустить без окна DOS

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий