'
+import os
from binascii import hexlify
from collections import OrderedDict
from PyQt4.Qt import (
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon,
- QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal)
+ QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal,
+ QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout)
-from calibre import human_readable
+from calibre import human_readable, sanitize_file_name_unicode
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS
from calibre.ebooks.oeb.polish.container import guess_type
from calibre.ebooks.oeb.polish.cover import get_cover_page_name, get_raster_cover_name
-from calibre.gui2 import error_dialog
+from calibre.gui2 import error_dialog, choose_files
from calibre.gui2.tweak_book import current_container
from calibre.gui2.tweak_book.editor import syntax_from_mime
+from calibre.gui2.tweak_book.templates import template_for
from calibre.utils.icu import sort_key
TOP_ICON_SIZE = 24
@@ -128,6 +131,14 @@ class FileList(QTreeWidget):
if name in state['selected']:
c.setSelected(True)
+ def select_name(self, name):
+ for parent in self.categories.itervalues():
+ for c in (parent.child(i) for i in xrange(parent.childCount())):
+ q = unicode(c.data(0, NAME_ROLE).toString())
+ c.setSelected(q == name)
+ if q == name:
+ self.scrollToItem(c)
+
def build(self, container, preserve_state=True):
if preserve_state:
state = self.get_state()
@@ -360,6 +371,86 @@ class FileList(QTreeWidget):
ans['selected'][name] = syntax_from_mime(mime)
return ans
+class NewFileDialog(QDialog):
+
+ def __init__(self, initial_choice='html', parent=None):
+ QDialog.__init__(self, parent)
+ self.l = l = QVBoxLayout()
+ self.setLayout(l)
+ self.la = la = QLabel(_(
+ 'Choose a name for the new file'))
+ self.setWindowTitle(_('Choose file'))
+ l.addWidget(la)
+ self.name = n = QLineEdit(self)
+ n.textChanged.connect(self.update_ok)
+ l.addWidget(n)
+ self.err_label = la = QLabel('')
+ la.setWordWrap(True)
+ l.addWidget(la)
+ self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ l.addWidget(bb)
+ bb.accepted.connect(self.accept)
+ bb.rejected.connect(self.reject)
+ self.imp_button = b = bb.addButton(_('Import resource file (image/font/etc.)'), bb.ActionRole)
+ b.setIcon(QIcon(I('view-image.png')))
+ b.clicked.connect(self.import_file)
+
+ self.ok_button = bb.button(bb.Ok)
+
+ self.file_data = ''
+ self.using_template = False
+
+ def show_error(self, msg):
+ self.err_label.setText('' + msg)
+ return False
+
+ def import_file(self):
+ path = choose_files(self, 'tweak-book-new-resource-file', _('Choose file'), select_only_single_file=True)
+ if path:
+ path = path[0]
+ with open(path, 'rb') as f:
+ self.file_data = f.read()
+ name = os.path.basename(path)
+ self.name.setText(name)
+
+ @property
+ def name_is_ok(self):
+ name = unicode(self.name.text())
+ if not name or not name.strip():
+ return self.show_error('')
+ ext = name.rpartition('.')[-1]
+ if not ext or ext == name:
+ return self.show_error(_('The file name must have an extension'))
+ norm = name.replace('\\', '/')
+ parts = name.split('/')
+ for x in parts:
+ if sanitize_file_name_unicode(x) != x:
+ return self.show_error(_('The file name contains invalid characters'))
+ if current_container().has_name(norm):
+ return self.show_error(_('This file name already exists in the book'))
+ self.show_error('')
+ return True
+
+ def update_ok(self, *args):
+ self.ok_button.setEnabled(self.name_is_ok)
+
+ def accept(self):
+ if not self.name_is_ok:
+ return error_dialog(self, _('No name specified'), _(
+ 'You must specify a name for the new file, with an extension, for example, chapter1.html'), show=True)
+ name = unicode(self.name.text())
+ name, ext = name.rpartition('.')[0::2]
+ name = (name + '.' + ext.lower()).replace('\\', '/')
+ mt = guess_type(name)
+ if mt in OEB_DOCS:
+ self.file_data = template_for('html').encode('utf-8')
+ self.using_template = True
+ elif mt in OEB_STYLES:
+ self.file_data = template_for('css').encode('utf-8')
+ self.using_template = True
+ self.file_name = name
+ QDialog.accept(self)
+
class FileListWidget(QWidget):
delete_requested = pyqtSignal(object, object)
@@ -375,7 +466,7 @@ class FileListWidget(QWidget):
self.layout().setContentsMargins(0, 0, 0, 0)
for x in ('delete_requested', 'reorder_spine', 'rename_requested', 'edit_file'):
getattr(self.file_list, x).connect(getattr(self, x))
- for x in ('delete_done',):
+ for x in ('delete_done', 'select_name'):
setattr(self, x, getattr(self.file_list, x))
def build(self, container, preserve_state=True):
diff --git a/src/calibre/gui2/tweak_book/templates.py b/src/calibre/gui2/tweak_book/templates.py
new file mode 100644
index 0000000000..5024819bb5
--- /dev/null
+++ b/src/calibre/gui2/tweak_book/templates.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8
+from __future__ import (unicode_literals, division, absolute_import,
+ print_function)
+
+__license__ = 'GPL v3'
+__copyright__ = '2013, Kovid Goyal '
+
+from calibre import prepare_string_for_xml
+from calibre.gui2.tweak_book import current_container
+
+DEFAULT_TEMPLATES = {
+ 'html':
+'''\
+
+
+
+ {TITLE}
+
+
+ %CURSOR%
+
+
+''',
+
+
+ 'css':
+'''\
+@charset utf-8;
+/* Styles for {TITLE} */
+%CURSOR%
+''',
+
+}
+
+def template_for(syntax):
+ mi = current_container().mi
+ data = {
+ 'TITLE':mi.title,
+ 'AUTHOR': ' & '.join(mi.authors),
+ }
+ template = DEFAULT_TEMPLATES.get(syntax, '')
+ return template.format(**{k:prepare_string_for_xml(v, True) for k, v in data.iteritems()})
+
diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py
index fb51ddd56d..822f6539f2 100644
--- a/src/calibre/gui2/tweak_book/ui.py
+++ b/src/calibre/gui2/tweak_book/ui.py
@@ -9,7 +9,7 @@ __copyright__ = '2013, Kovid Goyal '
from functools import partial
from PyQt4.Qt import (
- QDockWidget, Qt, QLabel, QIcon, QAction, QApplication, QWidget,
+ QDockWidget, Qt, QLabel, QIcon, QAction, QApplication, QWidget, QFontMetrics,
QVBoxLayout, QStackedWidget, QTabWidget, QImage, QPixmap, pyqtSignal)
from calibre.constants import __appname__, get_version
@@ -22,6 +22,10 @@ from calibre.gui2.tweak_book.keyboard import KeyboardManager
from calibre.gui2.tweak_book.preview import Preview
from calibre.gui2.tweak_book.search import SearchPanel
+def elided_text(font, text, width=200, mode=Qt.ElideMiddle):
+ fm = QFontMetrics(font)
+ return unicode(fm.elidedText(text, mode, int(width)))
+
class Central(QStackedWidget):
' The central widget, hosts the editors '
@@ -146,6 +150,9 @@ class Main(MainWindow):
self.keyboard.finalize()
self.keyboard.set_mode('other')
+ def elided_text(self, text, width=200, mode=Qt.ElideMiddle):
+ return elided_text(self.font(), text, width=width, mode=mode)
+
@property
def editor_tabs(self):
return self.central.editor_tabs
@@ -165,6 +172,7 @@ class Main(MainWindow):
self.addAction(ac)
return ac
+ self.action_new_file = reg('document-new.png', _('&New file'), self.boss.add_file, 'new-file', (), _('Create a new file in the current book'))
self.action_open_book = reg('document_open.png', _('Open &book'), self.boss.open_book, 'open-book', 'Ctrl+O', _('Open a new book'))
self.action_global_undo = reg('back.png', _('&Revert to before'), self.boss.do_global_undo, 'global-undo', 'Ctrl+Left',
_('Revert book to before the last action (Undo)'))
@@ -245,6 +253,7 @@ class Main(MainWindow):
b = self.menuBar()
f = b.addMenu(_('&File'))
+ f.addAction(self.action_new_file)
f.addAction(self.action_open_book)
f.addAction(self.action_save)
f.addAction(self.action_quit)
@@ -302,7 +311,7 @@ class Main(MainWindow):
return b
a = create(_('Book tool bar'), 'global').addAction
- for x in ('open_book', 'global_undo', 'global_redo', 'save', 'create_checkpoint', 'toc'):
+ for x in ('new_file', 'open_book', 'global_undo', 'global_redo', 'save', 'create_checkpoint', 'toc'):
a(getattr(self, 'action_' + x))
a = create(_('Polish book tool bar'), 'polish').addAction