def create_menubar(self): """建立一个菜单栏""" self.menubar = CreateFullMenu(self, self.history) self.grid.attach(self.menubar, 0, 0, 1, 1)
class TextViewWindow(Gtk.Window): """程序的主要部分""" history = [] historyListRange = 10 showLineNumber = False autoSave = False tabWidth = 4 timer_id = None def __init__(self): Gtk.Window.__init__(self, title="带背景的文本编辑器") GObject.signal_new("changed", BEditDocument, GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ()) self.grid = Gtk.Grid() self.add(self.grid) #载入历史记录 self.load_config() self.set_default_size(self.sizeWidth, self.sizeHeight) self.create_notebook() self.create_menubar() self.create_toolbar() self.create_find() self.on_new() self.set_style() # 图标来自 http://www.iconlet.com/info/85512_gedit-icon_128x128 self.set_icon_from_file(sys.path[0]+"/icons/bedit.png") def create_toolbar(self): """建立工具栏,这里的工具栏,实际上是选项卡上的一些标签页。""" self.button = [Gtk.ToolButton.new_from_stock(Gtk.STOCK_NEW), Gtk.MenuToolButton.new_from_stock(Gtk.STOCK_OPEN), Gtk.ToolButton.new_from_stock(Gtk.STOCK_SAVE), Gtk.ToolButton.new_from_stock(Gtk.STOCK_PRINT), Gtk.ToolButton.new_from_stock(Gtk.STOCK_UNDO), Gtk.ToolButton.new_from_stock(Gtk.STOCK_REDO), Gtk.ToolButton.new_from_stock(Gtk.STOCK_CUT), Gtk.ToolButton.new_from_stock(Gtk.STOCK_COPY), Gtk.ToolButton.new_from_stock(Gtk.STOCK_PASTE), Gtk.ToolButton.new_from_stock(Gtk.STOCK_FIND), Gtk.ToolButton.new_from_stock(Gtk.STOCK_PREFERENCES)] tool_box = Gtk.HBox() for i in self.button: tool_box.pack_start(i, False, False, 0) tool_box.show_all() self.notebook.append_page(Gtk.Box(spacing=6), tool_box) self.button[0].connect("clicked", self.on_new) self.button[1].connect("clicked", self.on_open_file) self.button[2].connect("clicked", self.on_save) self.button[4].connect("clicked", self.on_undo) self.button[5].connect("clicked", self.on_redo) self.button[9].connect("clicked", self.on_find) self.button[-1].connect("clicked", self.on_preferences) #历史记录 historymenu = Gtk.Menu() for i in self.history: new = Gtk.MenuItem.new_with_label(i) historymenu.append(new) new.show() new.connect("activate", self.open_with_menu) self.button[1].set_menu(historymenu) self.notebook.popup_enable() def create_menubar(self): """建立一个菜单栏""" self.menubar = CreateFullMenu(self, self.history) self.grid.attach(self.menubar, 0, 0, 1, 1) def create_find(self): self.findFindText = Gtk.Entry() self.findReplaceText = Gtk.Entry() self.frameBox = Gtk.HBox() button = Gtk.Button() button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) button.connect("clicked", self.close_find) self.frameBox.pack_start(button, False, False, 0) self.frameBox.pack_start(Gtk.Label("查找:"), False, False, 0) self.frameBox.pack_start(self.findFindText, False, False, 0) buttonPrev = Gtk.Button("上一个") buttonNext = Gtk.Button("下一个") buttonPrev.connect("clicked", self.find) buttonNext.connect("clicked", self.find) self.frameBox.pack_start(buttonPrev, False, False, 0) self.frameBox.pack_start(buttonNext, False, False, 0) self.frameBox.pack_start(Gtk.Label("替换:"), False, False, 0) self.frameBox.pack_start(self.findReplaceText, False, False, 0) buttonAllReplace = Gtk.Button("全部替换") buttonReplace = Gtk.Button("替换") buttonAllReplace.connect("clicked", self.all_replace) buttonReplace.connect("clicked", self.replace) self.frameBox.pack_start(buttonAllReplace, False, False, 0) self.frameBox.pack_start(buttonReplace, False, False, 0) self.grid.attach(self.frameBox, 0, 2, 1, 1) def set_language(self, menu = None, language = None): src_buffer = self.get_buffer() manager = GtkSource.LanguageManager() src_buffer.set_language(manager.get_language(language)) self.get_document().language = language self.on_update_title() def open_with_menu(self, menu = None): """通过历史菜单打开文件""" self.open_with_filename(menu.get_label()) def open_with_filename(self, name): # 判断最后一个标签里面是否是空文档 last_doc = self.notebook.get_nth_page(-1).get_child() if last_doc.filename != "无标题文档" or last_doc.change_number > 0 or last_doc.stext != "": self.on_new() last_doc = self.notebook.get_nth_page(-1).get_child() last_doc.open(name) last_doc.set_show_line_numbers(self.showLineNumber) last_doc.set_tab_width(self.tabWidth) self.append_history(name) self.save_config() def create_notebook(self): """建立选项卡""" self.notebook = Gtk.Notebook() self.grid.attach(self.notebook, 0, 1, 1, 1) def on_new(self, widget = None): """在这里新建一个文档""" new_textview = BEditDocument() new_textview.connect("changed", self.on_update_title) #拖放 new_textview.connect("drag-data-received", self.drag_data) new_scrolledwindow = Gtk.ScrolledWindow() new_scrolledwindow.set_hexpand(True) new_scrolledwindow.set_vexpand(True) new_scrolledwindow.add(new_textview) tab_box = self.new_label_with_icon_and_close_button("无标题文档", "python", new_scrolledwindow) self.notebook.append_page(new_scrolledwindow, tab_box) tab_box.show_all() #切换到刚插入的一页 self.show_all() self.notebook.set_current_page(self.notebook.get_n_pages()-1) def new_label_with_icon_and_close_button(self, label="", language="python", close_object=None): new_icon = Gtk.Image.new_from_file(sys.path[0]+"/icons/"+language+".png") new_label = Gtk.Label(label) button = Gtk.Button() button.set_relief(Gtk.ReliefStyle.NONE) button.set_focus_on_click(False) button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) button.connect("clicked", self.close_self, close_object) tab_box = Gtk.HBox() tab_box.pack_start(new_icon, False, False, 0) tab_box.pack_start(new_label, True, True, 0) tab_box.pack_start(button, False, False, 0) return tab_box def on_open_file(self, widget): dialog = Gtk.FileChooserDialog("打开文件", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) self.add_filters(dialog) if self.get_document().filepath != "": dialog.set_current_folder(self.get_document().filepath) response = dialog.run() if response == Gtk.ResponseType.OK: self.open_with_filename(dialog.get_filename()) dialog.destroy() def on_save(self, widget = None): cur_wid = self.notebook.get_nth_page(self.notebook.get_current_page()).get_child() # 判断文件是否有文件名 if cur_wid.filepath == "": self.on_update_title() return self.on_save_as(widget) else: # 直接使用已经存在的文件名 filename = cur_wid.save() self.append_history(filename) self.save_config() self.on_update_title() # 如果保存正确则返回 True return True def on_save_as(self, widget = None): """显示另存为对话框""" cur_wid = self.notebook.get_nth_page(self.notebook.get_current_page()).get_child() dialog = Gtk.FileChooserDialog("另存为", self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) self.add_filters(dialog) if self.get_document().filepath != "": dialog.set_current_folder(self.get_document().filepath) response = dialog.run() if response == Gtk.ResponseType.OK: cur_wid.save(dialog.get_filename()) self.append_history(dialog.get_filename()) self.save_config() dialog.destroy() return True dialog.destroy() return False def on_revert_to_saved(self, widget = None): cur_wid = self.notebook.get_nth_page(self.notebook.get_current_page()).get_child() if cur_wid.change_number != 0: dialog = Gtk.MessageDialog( type=Gtk.MessageType.QUESTION, message_format= '在关闭前将更改保存到文档“'+ cur_wid.filename+ '”吗?' ) dialog.format_secondary_text( "如果您不保存,前 "+ str((time.time()-cur_wid.change_timestamp+50)//60)[:-2]+ " 分钟内对文档所作的更改将永久丢失。" ) dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) dialog.add_button(Gtk.STOCK_REVERT_TO_SAVED, Gtk.ResponseType.OK) if dialog.run() == Gtk.ResponseType.OK: cur_wid.revert_to_saved() dialog.destroy() def on_close(self, widget = None): """关闭标签""" num = self.notebook.get_current_page() cur_wid = self.notebook.get_nth_page(num).get_child() # 检查文件时否保存 if cur_wid.change_number > 0: dialog = Gtk.MessageDialog( type=Gtk.MessageType.QUESTION, message_format= '是否放弃对文档"'+ cur_wid.filename+ '"所有的未保存更改?' ) dialog.format_secondary_text( "之前 "+ str((time.time()-cur_wid.change_timestamp+50)//60)[:-2]+ " 分钟内对文档所作的更改将永久丢失。" ) dialog.add_button("放弃更改并退出", Gtk.ResponseType.NO) dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) dialog.add_button(Gtk.STOCK_SAVE_AS, Gtk.ResponseType.YES) response = dialog.run() if response == Gtk.ResponseType.NO: self.notebook.remove_page(num) elif response == Gtk.ResponseType.YES: if self.on_save(): self.notebook.remove_page(num) dialog.destroy() else: self.notebook.remove_page(num) def close_self(self, button, data=None): self.notebook.remove_page(self.notebook.page_num(data)) def on_undo(self, menu): """撤销""" num = self.notebook.get_current_page() cur_wid = self.notebook.get_nth_page(num).get_child() if cur_wid.get_buffer().can_undo(): cur_wid.get_buffer().undo() def on_redo(self, menu): """重做""" num = self.notebook.get_current_page() cur_wid = self.notebook.get_nth_page(num).get_child() if cur_wid.get_buffer().can_redo(): cur_wid.get_buffer().redo() def on_find(self, widget = None): """搜索命令""" self.frameBox.show_all() def close_find(self, widget): self.frameBox.hide() def on_preferences(self, widget = None): """显示属性对话框""" dialog = PreferencesDialog(self) #还要从 css 中读取属性 file_object = open(os.environ['HOME']+"/.local/share/bedit/gtk-widgets3.css") try: text = file_object.read() # 将 css 文本交给对话框处理 dialog.set_css(text) finally: file_object.close() #写入其他内容 dialog.set_historyListRange(self.historyListRange) dialog.set_line_number(self.showLineNumber) dialog.setAutoSave(self.autoSave) response = dialog.run() file_object = open(os.environ['HOME']+"/.local/share/bedit/gtk-widgets3.css","w") try: file_object.write(dialog.get_css()) finally: file_object.close() #读取内容 self.historyListRange = dialog.get_historyListRange() self.showLineNumber = dialog.get_line_number() self.autoSave = dialog.getAutoSave() for i in range(1,self.notebook.get_n_pages()): self.notebook.get_nth_page(i).get_child().set_show_line_numbers(self.showLineNumber) dialog.destroy() #更新样式 self.set_style() def on_update_title(self, widget = None): """更新标题栏""" cwt = self.get_document() cw = self.notebook.get_nth_page(self.notebook.get_current_page()) if cwt.change_number == 0: self.set_title(cwt.filename+" ("+cwt.filepath+") - bedit") tab_box = self.new_label_with_icon_and_close_button(cwt.filename, cwt.language, cw) self.notebook.set_tab_label(cw, tab_box) tab_box.show_all() else: self.set_title("*" + cwt.filename+" ("+cwt.filepath+") - bedit") tab_box = self.new_label_with_icon_and_close_button("*"+cwt.filename, cwt.language, cw) self.notebook.set_tab_label(cw, tab_box) tab_box.show_all() def add_filters(self, dialog): filter_text = Gtk.FileFilter() filter_text.set_name("文本文件") filter_text.add_mime_type("text/plain") dialog.add_filter(filter_text) filter_py = Gtk.FileFilter() filter_py.set_name("Python 文件") filter_py.add_mime_type("text/x-python") dialog.add_filter(filter_py) filter_any = Gtk.FileFilter() filter_any.set_name("所有文件") filter_any.add_mime_type("*") dialog.add_filter(filter_any) def set_style(self): """用来设置外观""" style_provider = Gtk.CssProvider() style_provider.load_from_path(os.environ['HOME']+'/.local/share/bedit/gtk-widgets3.css') Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) def load_config(self): file_object = open(os.environ['HOME']+"/.local/share/bedit/config") try: for eachline in file_object: els = eachline.split() if els[0] == "wrap_mode": #读取换行模式 if els[1] == "NONE": self.wrap_mode = Gtk.WrapMode.NONE if els[1] == "CHAR": self.wrap_mode = Gtk.WrapMode.CHAR if els[1] == "WORD": self.wrap_mode = Gtk.WrapMode.WORD if els[1] == "WORD_CHAR": self.wrap_mode = Gtk.WrapMode.WORD_CHAR elif els[0] == "width": #读取窗口的长宽 self.sizeWidth = string.atoi(els[1]) elif els[0] == "height": self.sizeHeight = string.atoi(els[1]) elif els[0] == "historyListRange": #历史列表长度 self.historyListRange = string.atoi(els[1]) #是否显示行号 elif els[0] == "showLineNumber": self.showLineNumber = (els[1]=="True") #是否自动保存 elif els[0] == "autoSave": if els[1]=="True": self.autoSave = True self.timer_id = GObject.timeout_add(300000, self.saveBackup) else: self.autoSave = False else: #读取历史记录 self.history.append(eachline.strip('\n')) finally: file_object.close() def saveBackup(self): for i in range(1,self.notebook.get_n_pages()): self.notebook.get_nth_page(i).get_child() cur_wid = self.notebook.get_nth_page(i).get_child() # 判断文件是否有文件名 if cur_wid.filepath != "": cur_wid.saveBackup() return True def save_config(self): file_object = open(os.environ['HOME']+"/.local/share/bedit/config","w") try: if self.wrap_mode == Gtk.WrapMode.NONE: file_object.write("wrap_mode NONE\n") elif self.wrap_mode == Gtk.WrapMode.CHAR: file_object.write("wrap_mode CHAR\n") elif self.wrap_mode == Gtk.WrapMode.WORD: file_object.write("wrap_mode WORD\n") elif self.wrap_mode == Gtk.WrapMode.WORD_CHAR: file_object.write("wrap_mode WORD_CHAR\n") #保存大小 width, height = self.get_size() file_object.write("width "+str(width)+"\n") file_object.write("height "+str(height)+"\n") file_object.write("historyListRange "+str(self.historyListRange)+"\n") #是否显示行号 if self.showLineNumber: file_object.write("showLineNumber True\n") else: file_object.write("showLineNumber False\n") #是否自动保存 if self.autoSave: file_object.write("autoSave True\n") else: file_object.write("autoSave False\n") for eachline in self.history: file_object.write(eachline+'\n') finally: file_object.close() def main_quit(self, widget, event, data=None): self.save_config() Gtk.main_quit() def append_history(self, file): filemenu = self.menubar.get_children()[0].get_submenu() toolmenu = self.button[1].get_menu() if file in self.history: self.history.remove(file) #还要从文件菜单中移除 for i in filemenu.get_children(): if i.get_label() == file: filemenu.remove(i) for i in toolmenu.get_children(): if i.get_label() == file: toolmenu.remove(i) #插入菜单 new1 = Gtk.MenuItem.new_with_label(file) new2 = Gtk.MenuItem.new_with_label(file) filemenu.insert(new1,10) toolmenu.insert(new2,0) new1.show() new2.show() new1.connect("activate", self.open_with_menu) new2.connect("activate", self.open_with_menu) self.history.insert(0,file) self.history = self.history[0:self.historyListRange] def find(self, button = None): b = self.get_buffer() #搜索“上一个”和搜索“下一个”的区别在使用 start.forward_search 和 end.backward_search。 if button == None or button.get_label() == "下一个": area = self.get_document().find(self.findFindText.get_text(), "下一个") else: area = self.get_document().find(self.findFindText.get_text(), "上一个") # 获得选中位置的坐标,并且将滚动条滚动到这个位置。 adjustment = self.get_scrolledwindow().get_vadjustment() adjustment.set_value(area.y - adjustment.get_page_size()/2) self.get_scrolledwindow().set_vadjustment(adjustment) def replace(self, button): self.get_document().replace(self.escape(self.findReplaceText.get_text())) self.find() def all_replace(self, button): """全文替换""" self.get_document().all_replace(self.escape(self.findFindText.get_text()),self.escape(self.findReplaceText.get_text())) def drag_data(self, widget, context, x, y, data, info, time): files = data.get_text().rstrip('\n').split('\n') for fn in files: self.open_with_filename(urllib.unquote(fn[7:-1])) def get_buffer(self): cw = self.notebook.get_nth_page(self.notebook.get_current_page()) src_view = cw.get_child() return src_view.get_buffer() def get_document(self): cw = self.notebook.get_nth_page(self.notebook.get_current_page()) src_view = cw.get_child() return src_view def get_scrolledwindow(self): return self.notebook.get_nth_page(self.notebook.get_current_page()) def escape(self, string): """手动转换""" n = string.replace('\\n','\n') n = n.replace('\\t','\t') n = n.replace('\\v','\v') n = n.replace('\\0','\0') return n