def apply_current_editor_state(self): ed = self.gui.central.current_editor self.gui.cursor_position_widget.update_position() if ed is not None: actions['editor-undo'].setEnabled(ed.undo_available) actions['editor-redo'].setEnabled(ed.redo_available) actions['editor-copy'].setEnabled(ed.copy_available) actions['editor-cut'].setEnabled(ed.cut_available) actions['go-to-line-number'].setEnabled(ed.has_line_numbers) actions['fix-html-current'].setEnabled(ed.syntax == 'html') name = None for n, x in editors.iteritems(): if ed is x: name = n break if name is not None and getattr(ed, 'syntax', None) == 'html': if self.gui.preview.show(name): # The file being displayed by the preview has changed. # Set the preview's position to the current cursor # position in the editor, in case the editors' cursor # position has not changed, since the last time it was # focused. This is not inefficient since multiple requests # to sync are de-bounced with a 100 msec wait. self.sync_preview_to_editor() if name is not None: self.gui.file_list.mark_name_as_current(name) if ed.has_line_numbers: self.gui.cursor_position_widget.update_position( *ed.cursor_position) else: actions['go-to-line-number'].setEnabled(False) self.gui.file_list.clear_currently_edited_name()
def update_editors_from_container(self, container=None): c = container or current_container() for name, ed in tuple(editors.iteritems()): if c.has_name(name): ed.replace_data(c.raw_data(name)) else: self.close_editor(name)
def editor_action(self, action): ed = self.gui.central.current_editor for n, x in editors.iteritems(): if x is ed: edname = n break if hasattr(ed, 'action_triggered'): if action and action[0] == 'insert_resource': rtype = action[1] if rtype == 'image' and ed.syntax not in {'css', 'html'}: return error_dialog(self.gui, _('Not supported'), _( 'Inserting images is only supported for HTML and CSS files.'), show=True) rdata = get_resource_data(rtype, self.gui) if rdata is None: return if rtype == 'image': chosen_name, chosen_image_is_external = rdata if chosen_image_is_external: with open(chosen_image_is_external[1], 'rb') as f: current_container().add_file(chosen_image_is_external[0], f.read()) self.refresh_file_list() chosen_name = chosen_image_is_external[0] href = current_container().name_to_href(chosen_name, edname) ed.insert_image(href) else: ed.action_triggered(action)
def save_copy(self): c = current_container() ext = c.path_to_ebook.rpartition(".")[-1] path = choose_save_file( self.gui, "tweak_book_save_copy", _("Choose path"), filters=[(_("Book (%s)") % ext.upper(), [ext.lower()])], all_files=False, ) if not path: return tdir = self.mkdtemp(prefix="save-copy-") container = clone_container(c, tdir) for name, ed in editors.iteritems(): if ed.is_modified or not ed.is_synced_to_container: self.commit_editor_to_container(name, container) def do_save(c, path, tdir): save_container(c, path) shutil.rmtree(tdir, ignore_errors=True) return path self.gui.blocking_job( "save_copy", _("Saving copy, please wait..."), self.copy_saved, do_save, container, path, tdir )
def check_dirtied(self): dirtied = {name for name, ed in editors.iteritems() if ed.is_modified} if not dirtied: return True return question_dialog(self.gui, _('Unsaved changes'), _( 'You have unsaved changes in the files %s. If you proceed,' ' you will lose them. Proceed anyway?') % ', '.join(dirtied))
def apply_current_editor_state(self): ed = self.gui.central.current_editor self.gui.cursor_position_widget.update_position() if ed is not None: actions['editor-undo'].setEnabled(ed.undo_available) actions['editor-redo'].setEnabled(ed.redo_available) actions['editor-copy'].setEnabled(ed.copy_available) actions['editor-cut'].setEnabled(ed.cut_available) actions['go-to-line-number'].setEnabled(ed.has_line_numbers) actions['fix-html-current'].setEnabled(ed.syntax == 'html') name = None for n, x in editors.iteritems(): if ed is x: name = n break if name is not None and getattr(ed, 'syntax', None) == 'html': if self.gui.preview.show(name): # The file being displayed by the preview has changed. # Set the preview's position to the current cursor # position in the editor, in case the editors' cursor # position has not changed, since the last time it was # focused. This is not inefficient since multiple requests # to sync are de-bounced with a 100 msec wait. self.sync_preview_to_editor() if name is not None: self.gui.file_list.mark_name_as_current(name) if ed.has_line_numbers: self.gui.cursor_position_widget.update_position(*ed.cursor_position) else: actions['go-to-line-number'].setEnabled(False) self.gui.file_list.clear_currently_edited_name()
def tab_order(self): ans = [] rmap = {v: k for k, v in editors.iteritems()} for i in xrange(self.editor_tabs.count()): name = rmap.get(self.editor_tabs.widget(i)) if name is not None: ans.append(name) return ans
def update_editors_from_container(self, container=None): c = container or current_container() for name, ed in tuple(editors.iteritems()): if c.has_name(name): ed.replace_data(c.raw_data(name)) ed.is_synced_to_container = True else: self.close_editor(name)
def tab_order(self): ans = [] rmap = {v:k for k, v in editors.iteritems()} for i in xrange(self.editor_tabs.count()): name = rmap.get(self.editor_tabs.widget(i)) if name is not None: ans.append(name) return ans
def update_editors_from_container(self, container=None, names=None): c = container or current_container() for name, ed in tuple(editors.iteritems()): if c.has_name(name): if names is None or name in names: ed.replace_data(c.raw_data(name)) ed.is_synced_to_container = True else: self.close_editor(name)
def save_book(self): c = current_container() for name, ed in editors.iteritems(): if ed.is_modified or not ed.is_synced_to_container: self.commit_editor_to_container(name, c) ed.is_modified = False self.gui.action_save.setEnabled(False) tdir = self.mkdtemp(prefix='save-') container = clone_container(c, tdir) self.save_manager.schedule(tdir, container)
def editor_close_requested(self, editor): name = None for n, ed in editors.iteritems(): if ed is editor: name = n if not name: return if not editor.is_synced_to_container: self.commit_editor_to_container(name) self.close_editor(name)
def save_book(self): c = current_container() for name, ed in editors.iteritems(): if ed.is_modified: with c.open(name, 'wb') as f: f.write(ed.data) ed.is_modified = False self.gui.action_save.setEnabled(False) tdir = self.mkdtemp(prefix='save-') container = clone_container(c, tdir) self.save_manager.schedule(tdir, container)
def sync_preview_to_editor(self): if self.ignore_preview_to_editor_sync: return ed = self.gui.central.current_editor if ed is not None: name = None for n, x in editors.iteritems(): if ed is x: name = n break if name is not None and getattr(ed, 'syntax', None) == 'html': self.gui.preview.sync_to_editor(name, ed.current_line)
def editor_close_requested(self, editor): name = None for n, ed in editors.iteritems(): if ed is editor: name = n if not name: return if editor.is_modified: if not question_dialog(self.gui, _('Unsaved changes'), _( 'There are unsaved changes in %s. Are you sure you want to close' ' this editor?') % name): return self.close_editor(name)
def pretty_print(self, current): if current: ed = self.gui.central.current_editor for name, x in editors.iteritems(): if x is ed: break ed.pretty_print(name) else: self.commit_all_editors_to_container() with BusyCursor(): self.add_savepoint(_('Beautify files')) pretty_all(current_container()) self.update_editors_from_container() self.set_modified()
def apply_current_editor_state(self): ed = self.gui.central.current_editor if ed is not None: actions['editor-undo'].setEnabled(ed.undo_available) actions['editor-redo'].setEnabled(ed.redo_available) actions['editor-copy'].setEnabled(ed.copy_available) actions['editor-cut'].setEnabled(ed.cut_available) actions['go-to-line-number'].setEnabled(ed.has_line_numbers) actions['fix-html-current'].setEnabled(ed.syntax == 'html') name = None for n, x in editors.iteritems(): if ed is x: name = n break if name is not None and getattr(ed, 'syntax', None) == 'html': self.gui.preview.show(name) else: actions['go-to-line-number'].setEnabled(False)
def apply_current_editor_state(self, update_keymap=True): ed = self.gui.central.current_editor if ed is not None: actions['editor-undo'].setEnabled(ed.undo_available) actions['editor-redo'].setEnabled(ed.redo_available) actions['editor-save'].setEnabled(ed.is_modified) actions['editor-cut'].setEnabled(ed.copy_available) actions['editor-copy'].setEnabled(ed.cut_available) self.gui.keyboard.set_mode(ed.syntax) name = None for n, x in editors.iteritems(): if ed is x: name = n break if name is not None and getattr(ed, 'syntax', None) == 'html': self.gui.preview.show(name) else: self.gui.keyboard.set_mode('other')
def pretty_print(self, current): if current: ed = self.gui.central.current_editor for name, x in editors.iteritems(): if x is ed: break ed.pretty_print(name) else: if not self.check_dirtied(): return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: self.add_savepoint(_('Beautify files')) pretty_all(current_container()) self.update_editors_from_container() self.set_modified() finally: QApplication.restoreOverrideCursor()
def save_copy(self): c = current_container() ext = c.path_to_ebook.rpartition('.')[-1] path = choose_save_file(self.gui, 'tweak_book_save_copy', _('Choose path'), filters=[(_('Book (%s)') % ext.upper(), [ext.lower()])], all_files=False) if not path: return tdir = self.mkdtemp(prefix='save-copy-') container = clone_container(c, tdir) for name, ed in editors.iteritems(): if ed.is_modified or not ed.is_synced_to_container: self.commit_editor_to_container(name, container) def do_save(c, path, tdir): save_container(c, path) shutil.rmtree(tdir, ignore_errors=True) return path self.gui.blocking_job('save_copy', _('Saving copy, please wait...'), self.copy_saved, do_save, container, path, tdir)
def do_editor_save(self): ed = self.gui.central.current_editor if ed is None: return name = None for n, x in editors.iteritems(): if x is ed: name = n break if name is None: return c = current_container() with c.open(name, 'wb') as f: f.write(ed.data) ed.is_modified = False tdir = self.mkdtemp(prefix='save-') container = clone_container(c, tdir) self.save_manager.schedule(tdir, container) is_modified = False for ed in editors.itervalues(): if ed.is_modified: is_modified = True break self.gui.action_save.setEnabled(is_modified)
def search(self, action, overrides=None): ' Run a search/replace ' sp = self.gui.central.search_panel # Ensure the search panel is visible sp.setVisible(True) ed = self.gui.central.current_editor name = editor = None for n, x in editors.iteritems(): if x is ed: name = n break state = sp.state if overrides: state.update(overrides) searchable_names = self.gui.file_list.searchable_names where = state['where'] err = None if name is None and where in {'current', 'selected-text'}: err = _('No file is being edited.') elif where == 'selected' and not searchable_names['selected']: err = _('No files are selected in the Files Browser') elif where == 'selected-text' and not ed.has_marked_text: err = _( 'No text is marked. First select some text, and then use' ' The "Mark selected text" action in the Search menu to mark it.' ) if not err and not state['find']: err = _('No search query specified') if err: return error_dialog(self.gui, _('Cannot search'), err, show=True) del err files = OrderedDict() do_all = state['wrap'] or action in {'replace-all', 'count'} marked = False if where == 'current': editor = ed elif where in {'styles', 'text', 'selected'}: files = searchable_names[where] if name in files: # Start searching in the current editor editor = ed # Re-order the list of other files so that we search in the same # order every time. Depending on direction, search the files # that come after the current file, or before the current file, # first. lfiles = list(files) idx = lfiles.index(name) before, after = lfiles[:idx], lfiles[idx + 1:] if state['direction'] == 'up': lfiles = list(reversed(before)) if do_all: lfiles += list(reversed(after)) + [name] else: lfiles = after if do_all: lfiles += before + [name] files = OrderedDict((m, files[m]) for m in lfiles) else: editor = ed marked = True def no_match(): QApplication.restoreOverrideCursor() msg = '<p>' + _('No matches were found for %s.' ) % prepare_string_for_xml(state['find']) if not state['wrap']: msg += '<p>' + _( 'You have turned off search wrapping, so all text might not have been searched.' ' Try the search again, with wrapping enabled. Wrapping is enabled via the' ' "Wrap" checkbox at the bottom of the search panel.') return error_dialog(self.gui, _('Not found'), msg, show=True) pat = sp.get_regex(state) def do_find(): if editor is not None: if editor.find(pat, marked=marked): return if not files: if not state['wrap']: return no_match() return editor.find(pat, wrap=True, marked=marked) or no_match() for fname, syntax in files.iteritems(): if fname in editors: if not editors[fname].find(pat, complete=True): continue return self.show_editor(fname) raw = current_container().raw_data(fname) if pat.search(raw) is not None: self.edit_file(fname, syntax) if editors[fname].find(pat, complete=True): return return no_match() def no_replace(prefix=''): QApplication.restoreOverrideCursor() if prefix: prefix += ' ' error_dialog( self.gui, _('Cannot replace'), prefix + _('You must first click Find, before trying to replace'), show=True) return False def do_replace(): if editor is None: return no_replace() if not editor.replace(pat, state['replace']): return no_replace( _('Currently selected text does not match the search query.' )) return True def count_message(action, count): msg = _('%(action)s %(num)s occurrences of %(query)s' % dict(num=count, query=state['find'], action=action)) info_dialog(self.gui, _('Searching done'), prepare_string_for_xml(msg), show=True) def do_all(replace=True): count = 0 if not files and editor is None: return 0 lfiles = files or {name: editor.syntax} for n, syntax in lfiles.iteritems(): if n in editors: raw = editors[n].get_raw_data() else: raw = current_container().raw_data(n) if replace: raw, num = pat.subn(state['replace'], raw) else: num = len(pat.findall(raw)) count += num if replace and num > 0: if n in editors: editors[n].replace_data(raw) else: with current_container().open(n, 'wb') as f: f.write(raw.encode('utf-8')) QApplication.restoreOverrideCursor() count_message(_('Replaced') if replace else _('Found'), count) return count with BusyCursor(): if action == 'find': return do_find() if action == 'replace': return do_replace() if action == 'replace-find' and do_replace(): return do_find() if action == 'replace-all': if marked: return count_message( _('Replaced'), editor.all_in_marked(pat, state['replace'])) self.add_savepoint(_('Replace all')) count = do_all() if count == 0: self.rewind_savepoint() return if action == 'count': if marked: return count_message(_('Found'), editor.all_in_marked(pat)) return do_all(replace=False)
def commit_all_editors_to_container(self): with BusyCursor(): for name, ed in editors.iteritems(): if not ed.is_synced_to_container: self.commit_editor_to_container(name) ed.is_synced_to_container = True
def search(self, action, overrides=None): ' Run a search/replace ' sp = self.gui.central.search_panel # Ensure the search panel is visible sp.setVisible(True) ed = self.gui.central.current_editor name = editor = None for n, x in editors.iteritems(): if x is ed: name = n break state = sp.state if overrides: state.update(overrides) searchable_names = self.gui.file_list.searchable_names where = state['where'] err = None if name is None and where in {'current', 'selected-text'}: err = _('No file is being edited.') elif where == 'selected' and not searchable_names['selected']: err = _('No files are selected in the Files Browser') elif where == 'selected-text' and not ed.has_marked_text: err = _('No text is marked. First select some text, and then use' ' The "Mark selected text" action in the Search menu to mark it.') if not err and not state['find']: err = _('No search query specified') if err: return error_dialog(self.gui, _('Cannot search'), err, show=True) del err files = OrderedDict() do_all = state['wrap'] or action in {'replace-all', 'count'} marked = False if where == 'current': editor = ed elif where in {'styles', 'text', 'selected'}: files = searchable_names[where] if name in files: # Start searching in the current editor editor = ed # Re-order the list of other files so that we search in the same # order every time. Depending on direction, search the files # that come after the current file, or before the current file, # first. lfiles = list(files) idx = lfiles.index(name) before, after = lfiles[:idx], lfiles[idx+1:] if state['direction'] == 'up': lfiles = list(reversed(before)) if do_all: lfiles += list(reversed(after)) + [name] else: lfiles = after if do_all: lfiles += before + [name] files = OrderedDict((m, files[m]) for m in lfiles) else: editor = ed marked = True def no_match(): QApplication.restoreOverrideCursor() msg = '<p>' + _('No matches were found for %s.') % prepare_string_for_xml(state['find']) if not state['wrap']: msg += '<p>' + _('You have turned off search wrapping, so all text might not have been searched.' ' Try the search again, with wrapping enabled. Wrapping is enabled via the' ' "Wrap" checkbox at the bottom of the search panel.') return error_dialog( self.gui, _('Not found'), msg, show=True) pat = sp.get_regex(state) def do_find(): if editor is not None: if editor.find(pat, marked=marked): return if not files: if not state['wrap']: return no_match() return editor.find(pat, wrap=True, marked=marked) or no_match() for fname, syntax in files.iteritems(): if fname in editors: if not editors[fname].find(pat, complete=True): continue return self.show_editor(fname) raw = current_container().raw_data(fname) if pat.search(raw) is not None: self.edit_file(fname, syntax) if editors[fname].find(pat, complete=True): return return no_match() def no_replace(prefix=''): QApplication.restoreOverrideCursor() if prefix: prefix += ' ' error_dialog( self.gui, _('Cannot replace'), prefix + _( 'You must first click Find, before trying to replace'), show=True) return False def do_replace(): if editor is None: return no_replace() if not editor.replace(pat, state['replace']): return no_replace(_( 'Currently selected text does not match the search query.')) return True def count_message(action, count): msg = _('%(action)s %(num)s occurrences of %(query)s' % dict(num=count, query=state['find'], action=action)) info_dialog(self.gui, _('Searching done'), prepare_string_for_xml(msg), show=True) def do_all(replace=True): count = 0 if not files and editor is None: return 0 lfiles = files or {name:editor.syntax} for n, syntax in lfiles.iteritems(): if n in editors: raw = editors[n].get_raw_data() else: raw = current_container().raw_data(n) if replace: raw, num = pat.subn(state['replace'], raw) else: num = len(pat.findall(raw)) count += num if replace and num > 0: if n in editors: editors[n].replace_data(raw) else: with current_container().open(n, 'wb') as f: f.write(raw.encode('utf-8')) QApplication.restoreOverrideCursor() count_message(_('Replaced') if replace else _('Found'), count) return count with BusyCursor(): if action == 'find': return do_find() if action == 'replace': return do_replace() if action == 'replace-find' and do_replace(): return do_find() if action == 'replace-all': if marked: return count_message(_('Replaced'), editor.all_in_marked(pat, state['replace'])) self.add_savepoint(_('Replace all')) count = do_all() if count == 0: self.rewind_savepoint() return if action == 'count': if marked: return count_message(_('Found'), editor.all_in_marked(pat)) return do_all(replace=False)