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

Imported Upstream version 2.18.2+ds1

parent 7c7178dd
*.pyc
*~
build/
dist/
......@@ -2,6 +2,24 @@ ChangeLog of Frescobaldi, http://www.frescobaldi.org/
=====================================================
Changes in 2.18.2 -- December 26th, 2015
* Requirement changes:
- Frescobaldi now requires python-ly 0.9.3
* Improvements:
- More flexible colored HTML export and copy
- Tabs now show the push-pin icon when a document is always engraved
- Autocomplete correctly again after '\markup' without opening bracket
- Enable Ctrl-Enter in Custom Engrave dialog (issue #691)
* Bug fixes:
- fix AttributeError: 'SourceViewer' object has no attribute '_reply' (issue
#789)
- fix TypeError: QPen(): argument 1 has unexpected type QBrush
- fix some bugs in Quick Insert panel
* Translations:
- updated: Dutch, French, Italian, Ukrainian
Changes in 2.18.1 -- May 24th, 2015
* New feature:
......
include README* COPYING THANKS INSTALL TODO ChangeLog
include frescobaldi.desktop
include frescobaldi.1
include frescobaldi.png
include *.py
recursive-include frescobaldi_app README*
recursive-include frescobaldi_app *.png *.svg *.ico index.theme
recursive-include frescobaldi_app *.ly *.ily Makefile
recursive-include frescobaldi_app *.pot *.po *.mo
recursive-include frescobaldi_app *.dic
recursive-include frescobaldi_app *.js
recursive-include frescobaldi_app *.md
recursive-include macosx *.svg *.icns *.py *.strings *.sh *.png *.json *.diff
recursive-include windows *.bmp *.py
global-exclude *~
Metadata-Version: 1.1
Name: frescobaldi
Version: 2.18.2
Summary: LilyPond Music Editor
Home-page: http://www.frescobaldi.org/
Author: Wilbert Berendsen
Author-email: info@frescobaldi.org
License: GPL
Description: Frescobaldi is an advanced text editor to edit LilyPond sheet music files. Features include an integrated PDF preview and a powerful Score Wizard.
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: MacOS X
Classifier: Environment :: Win32 (MS Windows)
Classifier: Environment :: X11 Applications :: Qt
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Natural Language :: Chinese (Simplified)
Classifier: Natural Language :: Chinese (Traditional)
Classifier: Natural Language :: Czech
Classifier: Natural Language :: Dutch
Classifier: Natural Language :: English
Classifier: Natural Language :: French
Classifier: Natural Language :: Galician
Classifier: Natural Language :: German
Classifier: Natural Language :: Italian
Classifier: Natural Language :: Polish
Classifier: Natural Language :: Portuguese (Brazilian)
Classifier: Natural Language :: Russian
Classifier: Natural Language :: Spanish
Classifier: Natural Language :: Turkish
Classifier: Natural Language :: Ukranian
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Text Editors
......@@ -26,7 +26,7 @@ from __future__ import unicode_literals
# these variables are also used by the distutils setup
name = "frescobaldi"
version = "2.18.1"
version = "2.18.2"
description = "LilyPond Music Editor"
long_description = \
"Frescobaldi is an advanced text editor to edit LilyPond sheet music files. " \
......
......@@ -270,6 +270,14 @@ class Analyzer(object):
cursor = self.document_cursor()
return documentdata.doc(cursor.document()).markup(cursor)
def markup_top(self):
"""\\markup ... in music or toplevel"""
if self.last.startswith('\\') and isinstance(self.last,
(ly.lex.lilypond.MarkupCommand, ly.lex.lilypond.MarkupUserCommand)):
self.column = self.lastpos
cursor = self.document_cursor()
return documentdata.doc(cursor.document()).markup(cursor)
def header(self):
"""\\header {"""
if '=' in self.tokens[-3:] or self.last.startswith('\\'):
......@@ -465,19 +473,23 @@ class Analyzer(object):
# Mapping from Parsers to the lists of functions to run.
tests = {
lp.ParseGlobal: (
markup_top,
repeat,
toplevel,
),
lp.ParseBook: (
markup_top,
book,
),
lp.ParseBookPart: (
markup_top,
bookpart,
),
lp.ParseScore: (
score,
),
lp.ParseMusic: (
markup_top,
tweak,
scheme_word,
key,
......@@ -489,6 +501,19 @@ class Analyzer(object):
general_music,
),
lp.ParseNoteMode: (
markup_top,
tweak,
scheme_word,
key,
clef,
repeat,
accidental_style,
hide_omit,
revert,
general_music,
),
lp.ParseChordMode: (
markup_top,
tweak,
scheme_word,
key,
......@@ -503,6 +528,7 @@ class Analyzer(object):
markup,
),
lp.ParseHeader: (
markup_top,
header,
),
lp.ParsePaper: (
......@@ -522,6 +548,7 @@ class Analyzer(object):
context,
),
lp.ParseWith: (
markup_top,
engraver,
context_variable_set,
with_,
......@@ -576,6 +603,7 @@ class Analyzer(object):
font_name,
),
lp.ParseLyricMode: (
markup_top,
repeat,
lyricmode,
),
......
......@@ -71,8 +71,8 @@ class SourceViewer(QDialog):
def showReply(self, reply):
reply.setParent(self)
self.urlLabel.setText(reply.url().toString())
reply.finished.connect(self.loadingFinished)
self._reply = reply
reply.finished.connect(self.loadingFinished)
self.textbrowser.clear()
self.show()
......
......@@ -31,10 +31,8 @@ from PyQt4.QtGui import QItemSelectionModel, QMenu, QTreeWidget, QTreeWidgetItem
import app
import util
import qutil
import icons
import jobmanager
import jobattributes
import documenticon
import engrave
......@@ -103,19 +101,16 @@ class Widget(QTreeWidget):
self.setCurrentItem(self._items[doc], 0, QItemSelectionModel.ClearAndSelect)
def setDocumentStatus(self, doc):
i = self._items[doc]
try:
i = self._items[doc]
except KeyError:
# this fails when a document is closed that had a job running,
# in that case setDocumentStatus is called twice (the second time
# when the job quits, but then we already removed the document)
return
# set properties according to document
i.setText(0, doc.documentName())
job = jobmanager.job(doc)
if job and job.is_running() and not jobattributes.get(job).hidden:
icon = 'lilypond-run'
elif engrave.Engraver.instance(self.parentWidget().mainwindow()).stickyDocument() is doc:
icon = 'pushpin'
elif doc.isModified():
icon = 'document-save'
else:
icon = 'text-plain'
i.setIcon(0, icons.get(icon))
i.setIcon(0, documenticon.icon(doc, self.parentWidget().mainwindow()))
i.setToolTip(0, path(doc.url()))
# handle ordering in groups if desired
if self._group:
......
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
#
# Copyright (c) 2015 - 2015 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# See http://www.gnu.org/licenses/ for more information.
"""
Provides an icon for a Document.
"""
from __future__ import unicode_literals
import plugin
import signals
import jobmanager
import jobattributes
import engrave
import icons
def icon(doc, mainwindow=None):
"""Provides a QIcon for a Document.
If a MainWindow is provided, the sticky icon can be returned, if the
Document is sticky for that main window.
"""
return DocumentIconProvider.instance(doc).icon(mainwindow)
class DocumentIconProvider(plugin.DocumentPlugin):
"""Provides an icon for a Document."""
iconChanged = signals.Signal()
def __init__(self, doc):
doc.modificationChanged.connect(self._send_icon)
mgr = jobmanager.manager(doc)
mgr.started.connect(self._send_icon)
mgr.finished.connect(self._send_icon)
def _send_icon(self):
self.iconChanged()
def icon(self, mainwindow=None):
doc = self.document()
job = jobmanager.job(doc)
if job and job.is_running() and not jobattributes.get(job).hidden:
icon = 'lilypond-run'
elif mainwindow and doc is engrave.Engraver.instance(mainwindow).stickyDocument():
icon = 'pushpin'
elif doc.isModified():
icon = 'document-save'
else:
icon = 'text-plain'
return icons.get(icon)
......@@ -29,7 +29,7 @@ import app
import icons
import plugin
import engrave
import jobmanager
import documenticon
class DocumentMenu(QMenu):
......@@ -108,12 +108,8 @@ class DocumentActionGroup(QActionGroup, plugin.MainWindowPlugin):
# L10N: 'always engraved': the document is marked as 'Always Engrave' in the LilyPond menu
name += " " + _("[always engraved]")
self._acts[doc].setText(name)
# set the icon
if jobmanager.is_running(doc):
icon = icons.get('lilypond-run')
elif doc.isModified():
icon = icons.get('document-save')
else:
icon = documenticon.icon(doc, self.mainwindow())
if icon.name() == "text-plain":
icon = QIcon()
self._acts[doc].setIcon(icon)
......
......@@ -46,7 +46,7 @@ def engraver(mainwindow):
class Engraver(plugin.MainWindowPlugin):
stickyChanged = signals.Signal()
stickyChanged = signals.Signal() # Document
def __init__(self, mainwindow):
self._currentStickyDocument = None
......@@ -208,8 +208,11 @@ class Engraver(plugin.MainWindowPlugin):
if QSettings().value("lilypond_settings/save_on_run", False, bool):
doc = self.mainwindow().currentDocument()
if doc.isModified() and doc.url().toLocalFile():
doc.save()
try:
doc.save()
except IOError:
pass ## saving was not possible (e.g. happens when read only)
def runJob(self, job, document):
"""Runs the engraving job on behalf of document."""
jobattributes.get(job).mainwindow = self.mainwindow()
......
......@@ -33,7 +33,7 @@ The log is not displayed.
from __future__ import unicode_literals
from PyQt4.QtCore import QSettings, QTimer
from PyQt4.QtCore import QSettings, Qt, QTimer
import app
import documentinfo
......@@ -114,7 +114,7 @@ class AutoCompiler(plugin.MainWindowPlugin):
class AutoCompileManager(plugin.DocumentPlugin):
def __init__(self, document):
document.contentsChanged.connect(self.slotDocumentContentsChanged)
document.contentsChanged.connect(self.slotDocumentContentsChanged, Qt.QueuedConnection)
document.saved.connect(self.slotDocumentSaved)
document.loaded.connect(self.initialize)
jobmanager.manager(document).started.connect(self.slotJobStarted)
......@@ -124,6 +124,8 @@ class AutoCompileManager(plugin.DocumentPlugin):
document = self.document()
if document.isModified():
self._dirty = True
elif document.url().isEmpty():
self._dirty = False
else:
# look for existing result files in the default output format
s = QSettings()
......@@ -153,7 +155,8 @@ class AutoCompileManager(plugin.DocumentPlugin):
def slotDocumentContentsChanged(self):
"""Called when the user modifies the document."""
self._dirty = True
if self.document().isModified(): # not when a template was applied
self._dirty = True
def slotDocumentSaved(self):
"""Called when the document is saved. Forces auto-compile once."""
......
......@@ -210,6 +210,12 @@ class Dialog(QDialog):
os.path.basename(i.command), i.versionString(), document.documentName()))
return j
def keyPressEvent(self, ev):
if ev.key() == Qt.Key_Return and ev.modifiers() == Qt.ControlModifier:
self.accept()
else:
super(Dialog, self).keyPressEvent(ev)
Format = collections.namedtuple("Format", "type title options widgets")
......
......@@ -29,35 +29,42 @@ import ly.document
import ly.colorize
def html_text(text, mode=None, scheme='editor', inline=True, number_lines=False, full_html=True):
def html_text(text, mode=None, scheme='editor', inline=True, number_lines=False, full_html=True,
wrap_tag="pre", wrap_attrib="id", wrap_attrib_name="document"):
"""Converts the text to HTML using the specified or guessed mode."""
c = ly.document.Cursor(ly.document.Document(text, mode))
return html(c, scheme, inline, number_lines, full_html)
return html(c, scheme, inline, number_lines, full_html, wrap_tag, wrap_attrib, wrap_attrib_name)
def html_inline(cursor, scheme='editor', inline=True, number_lines=False, full_html=True):
def html_inline(cursor, scheme='editor', inline=True, number_lines=False,
full_html=True, wrap_tag="pre", wrap_attrib="id", wrap_attrib_name="document"):
"""Return an (by default) inline-styled HTML document for the cursor's selection."""
c = lydocument.cursor(cursor)
return html(c, scheme, inline, number_lines, full_html)
return html(c, scheme, inline, number_lines, full_html, wrap_tag, wrap_attrib, wrap_attrib_name)
def html_document(document, scheme='editor', inline=False, number_lines=False, full_html=True):
def html_document(document, scheme='editor', inline=False, number_lines=False, full_html=True,
wrap_tag="pre", wrap_attrib="id", wrap_attrib_name="document"):
"""Return a (by default) css-styled HTML document for the full document."""
c = lydocument.Cursor(lydocument.Document(document))
return html(c, scheme, inline, number_lines, full_html)
return html(c, scheme, inline, number_lines, full_html, wrap_tag, wrap_attrib, wrap_attrib_name)
def html(cursor, scheme='editor', inline=False, number_lines=False, full_html=True):
def html(cursor, scheme='editor', inline=False, number_lines=False, full_html=True,
wrap_tag="pre", wrap_attrib="id", wrap_attrib_name="document"):
"""Return a HTML document with the syntax-highlighted region.
The tokens are marked with <span> tags. The cursor is a
ly.document.Cursor instance. The specified text formats scheme is used
(by default 'editor'). If inline is True, the span tags have inline
style attributes. If inline is False, the span tags have class
The tokens are marked with <span> tags. The cursor is a
ly.document.Cursor instance. The specified text formats scheme is used
(by default 'editor'). If inline is True, the span tags have inline
style attributes. If inline is False, the span tags have class
attributes and a stylesheet is included.
Set number_lines to True to add line numbers.
"""
data = textformats.formatData(scheme) # the current highlighting scheme
w = ly.colorize.HtmlWriter()
w.set_wrapper_tag(wrap_tag)
w.set_wrapper_attribute(wrap_attrib)
w.document_id = wrap_attrib_name
w.inline_style = inline
w.number_lines = number_lines
w.full_html = full_html
......@@ -65,5 +72,3 @@ def html(cursor, scheme='editor', inline=False, number_lines=False, full_html=Tr
w.bgcolor = data.baseColors['background'].name()
w.css_scheme = data.css_scheme()
return w.html(cursor)
......@@ -36,10 +36,16 @@ import qutil
import userguide
import language_names
import widgets
import hyphdicts
import hyphenator
import po.setup
# If the folder was completely removed by a distributor
try:
import hyphdicts
except ImportError:
hyphdicts = None
# paths to check for hyphen dicts
default_paths = [
"share/hyphen",
......@@ -74,7 +80,8 @@ def directories():
else:
for pref in prefixes:
yield os.path.join(pref, path)
yield hyphdicts.path
if hyphdicts:
yield hyphdicts.path
return filter(os.path.isdir, gen())
def findDicts():
......
......@@ -51,7 +51,7 @@ def parse_commandline():
"""
import argparse
argparse._ = _ # let argparse use our translations
parser = argparse.ArgumentParser(
parser = argparse.ArgumentParser(conflict_handler="resolve",
description = _("A LilyPond Music Editor"))
parser.add_argument('-v', '--version', action="version",
version="{0} {1}".format(appinfo.appname, appinfo.version),
......@@ -78,7 +78,7 @@ def parse_commandline():
# Make sure debugger options are recognized as valid. These are passed automatically
# from PyDev in Eclipse to the inferior process.
if "pydevd" in sys.modules:
parser.add_argument('-v', '--vm_type')
parser.add_argument('--vm_type', '-v')
parser.add_argument('-a', '--client')
parser.add_argument('-p', '--port')
parser.add_argument('-f', '--file')
......@@ -159,7 +159,7 @@ def check_ly():
Importing ly.pkginfo is not expensive.
"""
ly_required = (0, 9)
ly_required = (0, 9, 3)
try:
import ly.pkginfo
version = tuple(map(int, re.findall(r"\d+", ly.pkginfo.version)))
......
This diff is collapsed.
No preview for this file type
This diff is collapsed.
No preview for this file type
This diff is collapsed.
No preview for this file type
This diff is collapsed.
No preview for this file type
This diff is collapsed.
This diff is collapsed.
No preview for this file type
This diff is collapsed.
No preview for this file type
This diff is collapsed.
No preview for this file type
This diff is collapsed.
No preview for this file type
This diff is collapsed.
This diff is collapsed.
No preview for this file type
This diff is collapsed.
No preview for this file type
This diff is collapsed.
No preview for this file type
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -25,7 +25,7 @@ from __future__ import unicode_literals
import itertools
from PyQt4.QtGui import QCheckBox, QHBoxLayout, QToolButton
from PyQt4.QtGui import QCheckBox, QHBoxLayout, QTextCursor, QToolButton
import app
import symbols
......@@ -226,13 +226,14 @@ def articulation_positions(cursor):
else:
rests = False
partial = ly.document.INSIDE
source = lydocument.Source(c, True, partial, True)
positions = []
for p in ly.rhythm.music_tokens(source):
if not rests and isinstance(p[0], ly.lex.lilypond.Rest):
for item in ly.rhythm.music_items(c, partial):
if not rests and item.tokens and isinstance(item.tokens[0], ly.lex.lilypond.Rest):
continue
positions.append(source.cursor(p[-1], start=len(p[-1])))
csr = QTextCursor(cursor.document())
csr.setPosition(item.end)
positions.append(csr)
if not cursor.hasSelection():
break # leave if first found, that's enough
return positions
......
......@@ -23,7 +23,7 @@ The Quick Insert panel dynamics Tool.
from __future__ import unicode_literals
from PyQt4.QtGui import QHBoxLayout, QToolButton
from PyQt4.QtGui import QHBoxLayout, QTextCursor, QToolButton
import app
import icons
......@@ -96,17 +96,18 @@ class Group(buttongroup.ButtonGroup):
# no, find the first pitch
c = lydocument.cursor(cursor)
c.end = None
source = lydocument.Source(c, True, ly.document.OUTSIDE, True)
for p in ly.rhythm.music_tokens(source):
cursor = source.cursor(p[-1], start=len(p[-1]))
for item in ly.rhythm.music_items(c, partial=ly.document.OUTSIDE):
cursor.setPosition(item.end)
break
cursor.insertText(direction + dynamic)
self.mainwindow().currentView().setTextCursor(cursor)
else:
c = lydocument.cursor(cursor)
source = lydocument.Source(c, True, tokens_with_position=True)
cursors = [source.cursor(p[-1], start=len(p[-1]))
for p in ly.rhythm.music_tokens(source)]
cursors = []
for item in ly.rhythm.music_items(c):
csr = QTextCursor(cursor.document())
csr.setPosition(item.end)
cursors.append(csr)
if not cursors:
return
c1, c2 = cursors[0], cursors[-1]
......
......@@ -23,7 +23,7 @@ The Quick Insert panel spanners Tool.
from __future__ import unicode_literals
from PyQt4.QtGui import QHBoxLayout, QToolButton
from PyQt4.QtGui import QHBoxLayout, QTextCursor, QToolButton
import app
import icons
......@@ -113,10 +113,10 @@ class ArpeggioGroup(buttongroup.ButtonGroup):
# where to insert
c = lydocument.cursor(cursor)
c.select_end_of_block()
source = lydocument.Source(c, True, ly.document.OUTSIDE, True)
with cursortools.compress_undo(cursor):
for p in ly.rhythm.music_tokens(source):
c = source.cursor(p[-1], start=len(p[-1]))
for item in ly.rhythm.music_items(c, partial=ly.document.OUTSIDE):
c = QTextCursor(cursor.document())
c.setPosition(item.end)
c.insertText('\\arpeggio')
if name != lastused:
cursortools.strip_indent(c)
......@@ -148,16 +148,17 @@ class GlissandoGroup(buttongroup.ButtonGroup):
style = _glissandoStyles[name]
c = lydocument.cursor(cursor)
c.select_end_of_block()
source = lydocument.Source(c, True, ly.document.OUTSIDE, True)
for p in ly.rhythm.music_tokens(source):
c = source.cursor(p[-1], start=len(p[-1]))
for item in ly.rhythm.music_items(c, partial=ly.document.OUTSIDE):
c = QTextCursor(cursor.document())
c.setPosition(item.end)
if style:
text = "-\\tweak #'style #'{0} \\glissando".format(style)
else:
text = '\\glissando'
c.insertText(text)
return
class SpannerGroup(buttongroup.ButtonGroup):
def translateUI(self):
self.setTitle(_("Spanners"))
......@@ -191,6 +192,7 @@ class SpannerGroup(buttongroup.ButtonGroup):
for s, c in zip(spanner, spanner_positions(cursor)):
c.insertText(s)
class GraceGroup(buttongroup.ButtonGroup):
def translateUI(self):
self.setTitle(_("Grace Notes"))
......@@ -249,14 +251,16 @@ class GraceGroup(buttongroup.ButtonGroup):
else:
c = lydocument.cursor(cursor)
c.end = None
source = lydocument.Source(c, True, ly.document.OUTSIDE, True)
music_list = list(ly.rhythm.music_tokens(source))
items = list(ly.rhythm.music_items(c, partial=ly.document.OUTSIDE))
after = self.mainwindow().textCursor()
try:
m = music_list[2][0]
after = source.cursor(m, 1)
except IndexError:
after = self.mainwindow().textCursor()
after.movePosition(cursor.EndOfLine)
i = items[2]
pos = i.pos + 1
end = (i.tokens or i.dur_tokens)[0].end
after.setPosition(pos)
after.setPosition(end, QTextCursor.KeepAnchor)