Commit fdf7ceeb authored by SVN-Git Migration's avatar SVN-Git Migration

Imported Upstream version 2.0.10+ds1

parent 0e1df5d4
......@@ -2,6 +2,69 @@ ChangeLog of Frescobaldi, http://www.frescobaldi.org/
=====================================================
Changes in 2.0.10 -- May 12th, 2013
* Translations:
- updated: nl, de, fr, cs, es, it
* New features:
- Document Outline tool with tooltips showing portions of the document.
The patterns that are used for the outline can be modified by the user.
* Improvements:
- Highlight more music functions that were added in LilyPond 2.16
- Better chord mode highlighting
* Bug fixes:
- fix QPyNullVariant error message on Mac OS X when setting helper app prefs
- fix Scorewizard error in Leadsheet with accompaniment and ambitus turned on
- fix #113: add files opened via file manager to recent files
- fix #143: don't count tremolo repeat as a duration
Changes in 2.0.9 -- March 23rd, 2013
* Translations:
- updated: nl, de, uk
* New features:
- Frescobaldi now detects when other applications modify or delete open files
and displays the changes in a dialog, where the user can reload or save the
affected documents. The file-watching is turned on by default, but can be
disabled. (wish: issue #103)
- File->Reload and Reload All: reload the current document or all documents
from disk. This action can be undone with Ctrl-Z.
- Frescobaldi now can be configured to open the generated PDF files when
opening a LilyPond document, even if they are not up-to-date. It then shows
a red background in the document chooser. See Preferences->Tools->Music View
- Music->Reload: switches the Music View to the current source document and
re-checks for updated PDF documents. If there are no updated PDFs it even
tries to load non up-to-date PDFs (regardless of the setting above)
- New --list-sessions commandline option to list the named sessions
- New actions View->Matching Pair and Select Matching Pair to jump to or
select the range of matching parentheses, braces etc (wish: issue #105)
- Quick Insert: \melisma, \melismaEnd spanner button (idea: issue #88)
* Improvements:
- Custom defined markup commands are also auto-completed
- Better default font on Windows
- Action "Always Engrave this document" also available in document context
menu (in documents list and in tabbar)
- Don't check included files multiple times for defined commands etc.
- Highlighting matching characters, such as slur, brace, << >>, etc does not
take a long time anymore when editing or moving through a long document
- string numbers are highlighted (and understood) correctly outside chords
(LilyPond 2.16 syntax change)
- Export colored HTML now uses CSS classes, makes it easy to change the high-
lighting in the HTML later (idea: issue #89)
* Bug fixes:
- Fix hyphenation of words with accents (reported by Andreas Edlund)
- Fix Save As... on Mac OS X (issue #104)
- Fix startup failure on Mac OS X (issue #77)
- Fix QPyNullVariant error messages with some PyQt versions
- Workaround two PyQt bugs:
* fix score wizard AttributeError message when using sip-4.14.3/PyQt-4.9.6
* fix large delays in editor when using sip-4.14.3/PyQt-4.9.6 (issue #100)
- music highlighting of a note after \unset someVariable is now correct
- fix Python error message when a document (marked as Always engraved) is
engraved which didn't have yet the PDF displayed
Changes in 2.0.8 -- September 14th, 2012
* Translations:
......
......@@ -45,11 +45,11 @@ module from pygame; or, as a last resort, embedding the PortMidi C-library via
ctypes. MIDI is optional.
Required:
Python:
Python (2.6 or 2.7):
http://www.python.org/
Qt4:
Qt4 (>= 4.7):
http://qt.nokia.com/
PyQt4:
PyQt4 (>= 4.8.3):
http://www.riverbankcomputing.co.uk/software/pyqt/
Poppler:
http://poppler.freedesktop.org/
......
......@@ -17,6 +17,7 @@ Features:
- Snippet Manager to store and apply text snippets, templates or scripts
- Use multiple versions of LilyPond, automatically selects the correct version
- Built-in LilyPond documentation browser and built-in help
- Configurable document outline view to navigate large LilyPond scores easily
- Modern user iterface with configurable colors, fonts and keyboard shortcuts
- Translated into: Dutch, English, French, German, Italian, Czech, Russian,
Spanish, Galician, Turkish, Polish, Brazillian and Ukrainian.
......
......@@ -17,6 +17,7 @@ Other developers:
French translation:
* Raphaël Doursenaud
* Denis Bitouzé
* Philippe Massart
* Valentin Villenave (valentin.villenave.info)
* Yann Collette
......@@ -42,6 +43,7 @@ German translation:
* Henrik Evers
* Georg Hennig
* Markus W. Kropp
* Urs Liska
Czech translation:
* Pavel Fric
......
TODO and wishes for Frescobaldi 2.x
===================================
- LilyPond context help
- wizard to download and unpack LilyPond documentation and/or LilyPond itself
......@@ -8,6 +9,7 @@ TODO and wishes for Frescobaldi 2.x
- Edit->Select Block
- Load .ly file specifying other encoding than UTF-8
(this can be done from the command line but not via the user interface)
- interface for running lilypond-book
......@@ -20,17 +22,21 @@ TODO and wishes for Frescobaldi 2.x
- check or even auto-insert bar checks
- document structure panel
- editing via Music View:
* relevant context menu on right-clicking object
* drag an object to e.g. create an \override #'extra-offset command
- Table of Contents support in qpopplerview
stub: qpopplerview/toc.py
Other ideas/wishes
==================
- continuous autocompile (run LilyPond whenever deemed suitable)
stub: engrave/autocompile.py
- support for Git/Hg/Svn diff/revert/commit
- play from cursor: plays music to MIDI output
......@@ -39,8 +45,9 @@ Other ideas/wishes
* context menu (with help, open filename, etc)
* if matching brace out of view, popup a small display of it (idea:
Franciso Vila)
* vi-mode
* vi-mode (stub: vimode/)
- define and extend api for script snippets
- make Frescobaldi api (using pydoc) browsable via the helpbrowser.
......@@ -68,7 +68,7 @@ def credits():
yield _(
"{appname} is translated into the following languages:").format(
appname=info.appname)
lang = QSettings().value("language", "") or None
lang = QSettings().value("language", "", type("")) or None
langs = [(language_names.languageName(code, lang), names)
for code, names in info.translators.items()]
for lang, names in sorted(langs):
......
......@@ -99,7 +99,7 @@ class ActionCollectionBase(object):
def settingsGroup(self):
"""Returns settings group to load/save shortcuts from or to."""
s = QSettings()
scheme = s.value("shortcut_scheme", "default")
scheme = s.value("shortcut_scheme", "default", type(""))
s.beginGroup("shortcuts/{0}/{1}".format(scheme, self.name))
return s
......@@ -168,7 +168,12 @@ class ActionCollection(ActionCollectionBase):
keys = settings.allKeys()
for name in keys:
try:
self._actions[name].setShortcuts([QKeySequence(s) for s in settings.value(name) or []])
shortcuts = settings.value(name, [], QKeySequence)
except TypeError:
# PyQt4 raises TypeError when an empty list was stored
shortcuts = []
try:
self._actions[name].setShortcuts(shortcuts)
except KeyError:
settings.remove(name)
if restoreDefaults:
......@@ -230,7 +235,11 @@ class ShortcutCollection(ActionCollectionBase):
# then load
settings = self.settingsGroup()
for name in settings.allKeys():
shortcuts = [QKeySequence(s) for s in settings.value(name) or []]
try:
shortcuts = settings.value(name, [], QKeySequence)
except TypeError:
# PyQt4 raises TypeError when an empty list was stored
shortcuts = []
if not shortcuts:
if not self.removeAction(name):
# if it did not exist, remove key from config
......
......@@ -40,7 +40,7 @@ QApplication.setOrganizationDomain(info.url)
windows = []
documents = []
from signals import Signal
from signals import Signal, SignalContext
# signals
aboutToQuit = Signal() # Use this and not qApp.aboutToQuit
......@@ -51,6 +51,8 @@ documentUrlChanged = Signal() # Document
documentLoaded = Signal() # Document
documentModificationChanged = Signal() # Document
documentClosed = Signal() # Document
documentSaved = Signal() # Document
documentSaving = SignalContext() # Document
viewCreated = Signal() # View
viewSpaceCreated = Signal() # ViewSpace (see viewmanager.py)
languageChanged = Signal() # (no arguments)
......@@ -150,10 +152,10 @@ def basedir():
import sessions
conf = sessions.currentSessionGroup()
if conf:
basedir = conf.value("basedir", "")
basedir = conf.value("basedir", "", type(""))
if basedir:
return basedir
return QSettings().value("basedir")
return QSettings().value("basedir", "", type(""))
def settings(name):
"""Returns a QSettings object referring a file in ~/.config/frescobaldi/"""
......
......@@ -41,7 +41,7 @@ class CompleterManager(plugin.MainWindowPlugin):
mainwindow.currentViewChanged.connect(self.setView)
if mainwindow.currentView():
self.setView(mainwindow.currentView())
complete = QSettings().value("autocomplete", True) not in ('false', False)
complete = QSettings().value("autocomplete", True, bool)
ac.autocomplete.setChecked(complete)
def setView(self, view):
......
......@@ -126,17 +126,20 @@ class Analyzer(object):
def book(self):
"""\\book {"""
self.backuntil(lx.Space)
return completiondata.lilypond_book
cursor = self.document_cursor()
return documentdata.doc(cursor.document()).bookcommands(cursor)
def bookpart(self):
"""\\bookpart {"""
self.backuntil(lx.Space)
return completiondata.lilypond_bookpart
cursor = self.document_cursor()
return documentdata.doc(cursor.document()).bookpartcommands(cursor)
def score(self):
"""\\score {"""
self.backuntil(lx.Space)
return completiondata.lilypond_score
cursor = self.document_cursor()
return documentdata.doc(cursor.document()).scorecommands(cursor)
def tweak(self):
"""complete property after \\tweak"""
......@@ -255,7 +258,7 @@ class Analyzer(object):
if m:
self.column = self.lastpos + m.start()
cursor = self.document_cursor()
return documentdata.doc(cursor.document()).markup()
return documentdata.doc(cursor.document()).markup(cursor)
def header(self):
"""\\header {"""
......@@ -341,10 +344,6 @@ class Analyzer(object):
# (only if we are in the override parser and there's no "=")
if isinstance(self.state.parser(), scm.ParseScheme):
return
if lp.EqualSignSetOverride in tokenclasses:
# TODO maybe return suitable values for the last property
self.backuntil(lp.EqualSignSetOverride, lx.Space)
return completiondata.lilypond_markup
self.backuntil(lp.DotSetOverride, lx.Space)
if (isinstance(self.state.parsers()[1], (
lp.ParseWith,
......@@ -369,14 +368,7 @@ class Analyzer(object):
"""\\set and \\unset"""
tokenclasses = self.tokenclasses()
self.backuntil(lx.Space, lp.DotSetOverride)
if lp.EqualSignSetOverride in tokenclasses:
# TODO maybe return suitable values for the context property
for t in self.tokens[::-1]:
if isinstance(t, (lp.EqualSignSetOverride, lx.Space)):
break
self.column = t.pos
return completiondata.lilypond_markup
elif lp.ContextProperty in tokenclasses and isinstance(self.last, lx.Space):
if lp.ContextProperty in tokenclasses and isinstance(self.last, lx.Space):
return # fall back to music?
elif lp.DotSetOverride in tokenclasses:
return completiondata.lilypond_context_properties
......
......@@ -49,7 +49,6 @@ markup = (
# these can occur (almost) everywhere
everywhere = (
'language',
'pointAndClickOn',
'pointAndClickOff',
'include',
......@@ -70,6 +69,7 @@ inputmodes = (
# commands that only occur at the global file level
toplevel = (
'language',
'version',
'sourcefileline',
'sourcefilename',
......@@ -135,7 +135,29 @@ toplevel_variables = (
'showFirstLength',
'showLastLength',
)
# stuff inside \score {}
score = sorted(
everywhere + inputmodes + start_music + blocks[1:] + (
'midi {',
))
# stuff inside \bookpart {}
bookpart = sorted(
everywhere + inputmodes + markup + start_music + modes[2:] + blocks
)
# stuff inside \book {}
book = sorted(
everywhere + inputmodes + markup + start_music
+ modes[1:] + blocks + (
'bookOutputName',
'bookOutputSuffix',
))
lilypond_markup = listmodel.ListModel(['\\markup'])
......@@ -186,21 +208,11 @@ lilypond_toplevel = listmodel.ListModel(sorted(itertools.chain(util.make_cmds(
+ modes + blocks
), toplevel_variables)), edit = util.cmd_or_var)
lilypond_book = listmodel.ListModel(sorted(
everywhere + inputmodes + markup + start_music
+ modes[1:] + blocks + (
'bookOutputName',
'bookOutputSuffix',
)), display = util.command)
lilypond_book = listmodel.ListModel(book, display = util.command)
lilypond_bookpart = listmodel.ListModel(sorted(
everywhere + inputmodes + markup + start_music + modes[2:] + blocks
), display = util.command)
lilypond_bookpart = listmodel.ListModel(bookpart, display = util.command)
lilypond_score = listmodel.ListModel(sorted(
everywhere + inputmodes + start_music + blocks[1:] + (
'midi {',
)), display = util.command)
lilypond_score = listmodel.ListModel(score, display = util.command)
lilypond_engravers = listmodel.ListModel(ly.data.engravers())
......
......@@ -30,6 +30,7 @@ import listmodel
import plugin
import ly.words
from . import completiondata
from . import harvest
from . import util
......@@ -59,12 +60,40 @@ class DocumentDataSource(plugin.DocumentPlugin):
return listmodel.ListModel(sorted(schemewords))
@util.keep
def markup(self):
def markup(self, cursor):
"""Completes markup commands and normal text from the document."""
return listmodel.ListModel(
['\\' + w for w in sorted(ly.words.markupcommands)]
+ [ '\\' + w for w in sorted(set(itertools.chain(
harvest.markup_commands(cursor),
harvest.include_markup_commands(cursor))))]
+ sorted(set(harvest.words(self.document()))))
@util.keep
def scorecommands(self, cursor):
"""Stuff inside \\score { }. """
return listmodel.ListModel(sorted(set(itertools.chain(
completiondata.score,
harvest.include_identifiers(cursor),
harvest.names(cursor)))), display = util.command)
@util.keep
def bookpartcommands(self, cursor):
"""Stuff inside \\bookpart { }. """
return listmodel.ListModel(sorted(set(itertools.chain(
completiondata.bookpart,
harvest.include_identifiers(cursor),
harvest.names(cursor)))), display = util.command)
@util.keep
def bookcommands(self, cursor):
"""Stuff inside \\book { }. """
return listmodel.ListModel(sorted(set(itertools.chain(
completiondata.book,
harvest.include_identifiers(cursor),
harvest.names(cursor)))), display = util.command)
@util.keep
def musiccommands(self, cursor):
return listmodel.ListModel(sorted(set(itertools.chain(
......@@ -82,6 +111,7 @@ class DocumentDataSource(plugin.DocumentPlugin):
def lyriccommands(self, cursor):
return listmodel.ListModel(sorted(set(itertools.chain(
('set stanza = ', 'set', 'override', 'markup', 'notemode'),
harvest.include_identifiers(cursor),
harvest.names(cursor)))), display = util.command)
def includenames(self, cursor, directory=None):
......
......@@ -33,6 +33,15 @@ import ly.lex.lilypond
import ly.lex.scheme
def tokens(cursor):
"""Yield the tokens tuple for every block from the beginning of the document until the cursor."""
end = cursor.block()
block = cursor.document().firstBlock()
while block < end:
yield tokeniter.tokens(block)
block = block.next()
def names(cursor):
"""Harvests names from assignments until the cursor."""
end = cursor.block()
......@@ -45,6 +54,11 @@ def names(cursor):
block = block.next()
def markup_commands(cursor):
"""Harvest markup command definitions until the cursor."""
return ly.parse.markup_commands(itertools.chain.from_iterable(tokens(cursor)))
def schemewords(document):
"""Harvests all schemewords from the document."""
for t in tokeniter.all_tokens(document):
......@@ -54,14 +68,7 @@ def schemewords(document):
def include_identifiers(cursor):
"""Harvests identifier definitions from included files."""
def tokens():
end = cursor.block()
block = cursor.document().firstBlock()
while block < end:
yield tokeniter.tokens(block)
block = block.next()
includeargs = ly.parse.includeargs(itertools.chain.from_iterable(tokens()))
includeargs = ly.parse.includeargs(itertools.chain.from_iterable(tokens(cursor)))
dinfo = documentinfo.info(cursor.document())
fname = cursor.document().url().toLocalFile()
files = fileinfo.includefiles(fname, dinfo.includepath(), includeargs)
......@@ -69,6 +76,16 @@ def include_identifiers(cursor):
for f in files)
def include_markup_commands(cursor):
"""Harvest markup command definitions from included files."""
includeargs = ly.parse.includeargs(itertools.chain.from_iterable(tokens(cursor)))
dinfo = documentinfo.info(cursor.document())
fname = cursor.document().url().toLocalFile()
files = fileinfo.includefiles(fname, dinfo.includepath(), includeargs)
return itertools.chain.from_iterable(fileinfo.FileInfo.info(f).markup_commands()
for f in files)
_words = re.compile(r'\w{5,}|\w{2,}(?:[:-]\w+)+').finditer
_word_types = (
ly.lex.String, ly.lex.Comment, ly.lex.Unparsed,
......
......@@ -46,7 +46,7 @@ def backup(filename):
def removeBackup(filename):
"""Removes filename's backup unless the user has configured to keep it."""
if filename and QSettings().value("backup_keep", False) in (False, 'false'):
if filename and not QSettings().value("backup_keep", False, bool):
try:
os.remove(backupName(filename))
except (IOError, OSError):
......
......@@ -70,7 +70,7 @@ class Widget(QWidget):
self.blockCombo.setModel(model)
# load block setting
name = QSettings().value("charmaptool/last_block", "")
name = QSettings().value("charmaptool/last_block", "", type(""))
if name:
for i, b in enumerate(_blocks):
if b.name == name:
......@@ -87,11 +87,11 @@ class Widget(QWidget):
s = QSettings()
s.beginGroup("charmaptool")
font = self.font()
family = s.value("fontfamily", "")
family = s.value("fontfamily", "", type(""))
if family:
font.setFamily(family)
self.charmap.charmap.setDisplayFont(font)
size = float(s.value("fontsize", font.pointSizeF()))
size = s.value("fontsize", font.pointSizeF(), float)
self.charmap.charmap.setDisplayFontSizeF(size)
def updateBlock(self):
......
......@@ -26,7 +26,7 @@ from __future__ import unicode_literals
from PyQt4.QtGui import QTextFormat
import ly.lex
import highlighter
import highlight2html
import textformats
......@@ -37,39 +37,17 @@ def colorize(text, state=None):
"""Converts the text to HTML using the specified or guessed state."""
if state is None:
state = ly.lex.guessState(text)
result = []
h = highlighter.highlightFormats()
d = textformats.formatData('editor')
result.append('<pre style="color: {0}; background: {1}; '
'font-family: &quot;{2}&quot;; font-size: {3}pt;">'.format(
d.baseColors['text'].name(), d.baseColors['background'].name(),
d.font.family(), d.font.pointSizeF()))
for t in state.tokens(text):
f = h.format(t)
if f:
s = style(f)
if s:
result.append('<span style="{0}">{1}</span>'.format(s, escape(t)))
continue
result.append(escape(t))
data = textformats.formatData('editor')
h = highlight2html.HtmlHighlighter(data, inline_style=True)
result = [
'<pre style="color: {0}; background: {1}; font-family: {2}">'.format(
data.baseColors['text'].name(),
data.baseColors['background'].name(),
data.font.family())]
result.extend(map(h.html_for_token, state.tokens(text)))
result.append('</pre>')
return ''.join(result)
def escape(text):
return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
def style(f):
s = []
if f.hasProperty(QTextFormat.ForegroundBrush):
s.append('color: {0};'.format(f.foreground().color().name()))
if f.hasProperty(QTextFormat.BackgroundBrush):
s.append('color: {0};'.format(f.background().color().name()))
if f.hasProperty(QTextFormat.FontWeight):
s.append('font-weight: {0};'.format(f.fontWeight() * 8))
if f.fontItalic():
s.append('font-style: italic;')
return ' '.join(s)
......@@ -59,7 +59,11 @@ class Model(QStringListModel):
self.load()
def load(self):
self.setStringList(sorted(QSettings().value(self.key) or []))
try:
strings = QSettings().value(self.key, [], type(""))
except TypeError:
strings = []
self.setStringList(sorted(strings))
self._changed = False
def save(self):
......
......@@ -23,10 +23,8 @@ Updates a document using convert-ly.
from __future__ import unicode_literals
import difflib
import textwrap
import os
import re
import subprocess
from PyQt4.QtCore import QSettings, QSize
......@@ -38,6 +36,7 @@ import app
import util
import qutil
import widgets
import htmldiff
import cursordiff
import lilypondinfo
import documentinfo
......@@ -81,7 +80,7 @@ class Dialog(QDialog):
self.messages = QTextBrowser()
self.diff = QTextBrowser(lineWrapMode=QTextBrowser.NoWrap)
self.copyCheck = QCheckBox(checked=
QSettings().value('convert_ly/copy_messages', True) not in (False, 'false'))
QSettings().value('convert_ly/copy_messages', True, bool))
self.tabw = QTabWidget()
self.tabw.addTab(self.messages, '')
......@@ -151,7 +150,10 @@ class Dialog(QDialog):
self._convertedtext = text
self.buttons.button(QDialogButtonBox.Ok).setEnabled(bool(text))
if text:
self.diff.setHtml(makeHtmlDiff(self._text, text))
self.diff.setHtml(htmldiff.htmldiff(
self._text, text,
_("Current Document"), _("Converted Document"),
wrapcolumn=100))
else:
self.diff.clear()
......@@ -192,7 +194,7 @@ class Dialog(QDialog):
# if the user wants english messages, do it also here: LANGUAGE=C
env = None
if QSettings().value("lilypond_settings/no_translation", False) in (True, "true"):
if QSettings().value("lilypond_settings/no_translation", False, bool):
if os.name == "nt":
# Python 2.7 subprocess on Windows chokes on unicode in env
env = util.bytes_environ()
......@@ -221,79 +223,3 @@ class Dialog(QDialog):
self.messages.append('\n' + _("The document has not been changed."))
def makeHtmlDiff(old, new):
table = difflib.HtmlDiff(wrapcolumn=100).make_table(
old.splitlines(), new.splitlines(),
_("Current Document"), _("Converted Document"), True, 3)
# overcome a QTextBrowser limitation (no text-align css support)
table = table.replace('<td class="diff_header"', '<td align="right" class="diff_header"')
# make horizontal lines between sections
table = re.sub(r'</tbody>\s*<tbody>', '<tr><td colspan="6"><hr/></td></tr>', table)
legend = _legend.format(
colors = _("Colors:"),
added = _("Added"),
changed = _("Changed"),
deleted = _("Deleted"),
links = _("Links:"),
first_change = _("First Change"),
next_change = _("Next Change"),
top = _("Top"))