~ubuntu-branches/ubuntu/vivid/frescobaldi/vivid

« back to all changes in this revision

Viewing changes to frescobaldi_app/mainwindow.py

  • Committer: Package Import Robot
  • Author(s): Ryan Kavanagh
  • Date: 2012-01-03 16:20:11 UTC
  • mfrom: (1.4.1)
  • Revision ID: package-import@ubuntu.com-20120103162011-tsjkwl4sntwmprea
Tags: 2.0.0-1
* New upstream release 
* Drop the following uneeded patches:
  + 01_checkmodules_no_python-kde4_build-dep.diff
  + 02_no_pyc.diff
  + 04_no_binary_lilypond_upgrades.diff
* Needs new dependency python-poppler-qt4
* Update debian/watch for new download path
* Update copyright file with new holders and years

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
 
2
#
 
3
# Copyright (c) 2008 - 2011 by Wilbert Berendsen
 
4
#
 
5
# This program is free software; you can redistribute it and/or
 
6
# modify it under the terms of the GNU General Public License
 
7
# as published by the Free Software Foundation; either version 2
 
8
# of the License, or (at your option) any later version.
 
9
#
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program; if not, write to the Free Software
 
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
18
# See http://www.gnu.org/licenses/ for more information.
 
19
 
 
20
"""
 
21
Frescobaldi Main Window.
 
22
"""
 
23
 
 
24
from __future__ import unicode_literals
 
25
 
 
26
import itertools
 
27
import os
 
28
import weakref
 
29
 
 
30
from PyQt4.QtCore import *
 
31
from PyQt4.QtGui import *
 
32
 
 
33
import app
 
34
import backup
 
35
import info
 
36
import icons
 
37
import actioncollection
 
38
import actioncollectionmanager
 
39
import menu
 
40
import tabbar
 
41
import document
 
42
import view
 
43
import viewmanager
 
44
import highlighter
 
45
import historymanager
 
46
import recentfiles
 
47
import sessions.manager
 
48
import util
 
49
import helpers
 
50
import panels
 
51
import engrave
 
52
import scorewiz
 
53
 
 
54
 
 
55
class MainWindow(QMainWindow):
 
56
    
 
57
    # both signals emit (current, previous)
 
58
    currentDocumentChanged = pyqtSignal(document.Document, document.Document)
 
59
    currentViewChanged = pyqtSignal(view.View, view.View)
 
60
    
 
61
    # emitted when whether there is a selection changes
 
62
    selectionStateChanged = pyqtSignal(bool)
 
63
    
 
64
    def __init__(self, other=None):
 
65
        """Creates a new MainWindow.
 
66
        
 
67
        It adds itself to app.windows to keep a reference.
 
68
        It shares the documents list with all other MainWindows. It copies
 
69
        some info (like the currently active document) from the 'other' window,
 
70
        if given.
 
71
        
 
72
        """
 
73
        QMainWindow.__init__(self)
 
74
        self.setAttribute(Qt.WA_DeleteOnClose)
 
75
        
 
76
        # this could be made configurable
 
77
        self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea)
 
78
        self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea)
 
79
        self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea)
 
80
        self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea)
 
81
        
 
82
        self._currentDocument = None
 
83
        self._currentView = lambda: None
 
84
        self._selectedState = None
 
85
        
 
86
        # find an unused objectName
 
87
        names = set(win.objectName() for win in app.windows)
 
88
        for num in itertools.count(1):
 
89
            name = "MainWindow{0}".format(num)
 
90
            if name not in names:
 
91
                self.setObjectName(name)
 
92
                break
 
93
        
 
94
        self.setWindowIcon(icons.get('frescobaldi'))
 
95
        app.windows.append(self)
 
96
        
 
97
        mainwidget = QWidget()
 
98
        self.setCentralWidget(mainwidget)
 
99
        layout = QVBoxLayout()
 
100
        layout.setContentsMargins(0, 0, 0, 0)
 
101
        layout.setSpacing(0)
 
102
        mainwidget.setLayout(layout)
 
103
        self.tabBar = tabbar.TabBar(self)
 
104
        self.viewManager = viewmanager.ViewManager(self)
 
105
        layout.addWidget(self.tabBar)
 
106
        layout.addWidget(self.viewManager)
 
107
 
 
108
        self.createActions()
 
109
        self.createMenus()
 
110
        self.createToolBars()
 
111
        
 
112
        app.translateUI(self)
 
113
        app.sessionChanged.connect(self.updateWindowTitle)
 
114
        
 
115
        self.readSettings()
 
116
        
 
117
        self.historyManager = historymanager.HistoryManager(self, other.historyManager if other else None)
 
118
        self.viewManager.viewChanged.connect(self.slotViewChanged)
 
119
        self.tabBar.currentDocumentChanged.connect(self.setCurrentDocument)
 
120
        self.setAcceptDrops(True)
 
121
        
 
122
        # keep track of all ActionCollections for the keyboard settings dialog
 
123
        actioncollectionmanager.manager(self).addActionCollection(self.actionCollection)
 
124
        actioncollectionmanager.manager(self).addActionCollection(self.viewManager.actionCollection)
 
125
        
 
126
        if other:
 
127
            self.setCurrentDocument(other.currentDocument())
 
128
        app.mainwindowCreated(self)
 
129
        
 
130
    def documents(self):
 
131
        """Returns the list of documents in the order of the TabBar."""
 
132
        return self.tabBar.documents()
 
133
        
 
134
    def currentView(self):
 
135
        """Returns the current View or None."""
 
136
        return self._currentView()
 
137
    
 
138
    def currentDocument(self):
 
139
        """Returns the current Document or None."""
 
140
        return self._currentDocument
 
141
        
 
142
    def setCurrentDocument(self, doc, findOpenView=False):
 
143
        self.viewManager.setCurrentDocument(doc, findOpenView)
 
144
    
 
145
    def hasSelection(self):
 
146
        """Returns whether there is a selection."""
 
147
        return self.textCursor().hasSelection() if self.currentView() else False
 
148
            
 
149
    def textCursor(self):
 
150
        """Returns the QTextCursor of the current View.
 
151
        
 
152
        Raises an error if there is not yet a view.
 
153
        
 
154
        """
 
155
        return self.currentView().textCursor()
 
156
        
 
157
    def setTextCursor(self, cursor, findOpenView=False):
 
158
        """Switches to the document() of the cursor and then sets that cursor on its View."""
 
159
        self.setCurrentDocument(cursor.document(), findOpenView)
 
160
        self.currentView().setTextCursor(cursor)
 
161
    
 
162
    def slotViewChanged(self, view):
 
163
        curv = self._currentView()
 
164
        if curv:
 
165
            if curv is view:
 
166
                return
 
167
            curv.copyAvailable.disconnect(self.updateSelection)
 
168
            curv.selectionChanged.disconnect(self.updateSelection)
 
169
        view.copyAvailable.connect(self.updateSelection)
 
170
        view.selectionChanged.connect(self.updateSelection)
 
171
        self._currentView = weakref.ref(view)
 
172
        
 
173
        doc = view.document()
 
174
        curd, self._currentDocument = self._currentDocument, doc
 
175
        if curd is not doc:
 
176
            if curd:
 
177
                curd.undoAvailable.disconnect(self.updateDocActions)
 
178
                curd.redoAvailable.disconnect(self.updateDocActions)
 
179
                curd.modificationChanged.disconnect(self.updateWindowTitle)
 
180
                curd.urlChanged.disconnect(self.updateWindowTitle)
 
181
                curd.loaded.disconnect(self.updateDocActions)
 
182
            doc.undoAvailable.connect(self.updateDocActions)
 
183
            doc.redoAvailable.connect(self.updateDocActions)
 
184
            doc.modificationChanged.connect(self.updateWindowTitle)
 
185
            doc.urlChanged.connect(self.updateWindowTitle)
 
186
            doc.loaded.connect(self.updateDocActions)
 
187
            self.updateDocActions()
 
188
            self.updateWindowTitle()
 
189
        self.updateSelection()
 
190
        self.currentViewChanged.emit(view, curv)
 
191
        if curd is not doc:
 
192
            self.currentDocumentChanged.emit(doc, curd)
 
193
    
 
194
    def updateSelection(self):
 
195
        selection = self.textCursor().hasSelection()
 
196
        if selection != self._selectedState:
 
197
            self._selectedState = selection
 
198
            self.selectionStateChanged.emit(selection)
 
199
            ac = self.actionCollection
 
200
            ac.edit_copy.setEnabled(selection)
 
201
            ac.edit_copy_colored_html.setEnabled(selection)
 
202
            ac.edit_cut.setEnabled(selection)
 
203
            ac.edit_select_none.setEnabled(selection)
 
204
    
 
205
    def updateDocActions(self):
 
206
        doc = self.currentDocument()
 
207
        ac = self.actionCollection
 
208
        ac.edit_undo.setEnabled(doc.isUndoAvailable())
 
209
        ac.edit_redo.setEnabled(doc.isRedoAvailable())
 
210
        
 
211
    def updateWindowTitle(self):
 
212
        doc = self.currentDocument()
 
213
        name = []
 
214
        if sessions.currentSession():
 
215
            name.append(sessions.currentSession() + ':')
 
216
        if doc:
 
217
            if doc.url().isEmpty():
 
218
                name.append(doc.documentName())
 
219
            elif doc.url().toLocalFile():
 
220
                name.append(util.homify(doc.url().toLocalFile()))
 
221
            else:
 
222
                name.append(doc.url().toString())
 
223
            if doc.isModified():
 
224
                # L10N: state of document in window titlebar
 
225
                name.append(_("[modified]"))
 
226
        self.setWindowTitle(app.caption(" ".join(name)))
 
227
    
 
228
    def dropEvent(self, ev):
 
229
        if not ev.source() and ev.mimeData().hasUrls():
 
230
            ev.accept()
 
231
            docs = [self.openUrl(url) for url in ev.mimeData().urls()]
 
232
            if docs:
 
233
                self.setCurrentDocument(docs[-1])
 
234
        
 
235
    def dragEnterEvent(self, ev):
 
236
        if not ev.source() and ev.mimeData().hasUrls():
 
237
            ev.accept()
 
238
        
 
239
    def closeEvent(self, ev):
 
240
        lastWindow = len(app.windows) == 1
 
241
        if lastWindow:
 
242
            sessions.manager.get(self).saveCurrentSessionIfDesired()
 
243
            self.writeSettings()
 
244
        if not lastWindow or self.queryClose():
 
245
            app.windows.remove(self)
 
246
            app.mainwindowClosed(self)
 
247
            ev.accept()
 
248
        else:
 
249
            ev.ignore()
 
250
 
 
251
    def queryClose(self):
 
252
        """Tries to close all documents, returns True if succeeded."""
 
253
        for doc in self.historyManager.documents():
 
254
            if not self.queryCloseDocument(doc):
 
255
                return False
 
256
        for doc in self.historyManager.documents()[::-1]:
 
257
            doc.close()
 
258
        return True
 
259
 
 
260
    def queryCloseDocument(self, doc):
 
261
        """Returns whether a document can be closed.
 
262
        
 
263
        If modified, asks the user. The document is not closed.
 
264
        """
 
265
        if not doc.isModified():
 
266
            return True
 
267
        self.setCurrentDocument(doc, findOpenView=True)
 
268
        res = QMessageBox.warning(self, _("dialog title", "Close Document"),
 
269
            _("The document \"{name}\" has been modified.\n"
 
270
            "Do you want to save your changes or discard them?").format(name=doc.documentName()),
 
271
            QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
 
272
        if res == QMessageBox.Save:
 
273
            return self.saveDocument(doc)
 
274
        else:
 
275
            return res == QMessageBox.Discard
 
276
        
 
277
    def createPopupMenu(self):
 
278
        """ Adds an entry to the popup menu to show/hide the tab bar. """
 
279
        menu = QMainWindow.createPopupMenu(self)
 
280
        menu.addSeparator()
 
281
        a = menu.addAction(_("Tab Bar"))
 
282
        a.setCheckable(True)
 
283
        a.setChecked(self.tabBar.isVisible())
 
284
        a.toggled.connect(self.tabBar.setVisible)
 
285
        return menu
 
286
        
 
287
    def readSettings(self):
 
288
        """ Read a few settings from the application global config. """
 
289
        settings = QSettings()
 
290
        settings.beginGroup('mainwindow')
 
291
        defaultSize = QApplication.desktop().screen().size() * 2 / 3
 
292
        self.resize(settings.value("size", defaultSize))
 
293
        self.restoreState(settings.value('state', QByteArray()))
 
294
        self.tabBar.setVisible(settings.value('tabbar', True)
 
295
            not in (False, "false"))
 
296
        
 
297
    def writeSettings(self):
 
298
        """ Write a few settings to the application global config. """
 
299
        settings = QSettings()
 
300
        settings.beginGroup('mainwindow')
 
301
        if not self.isFullScreen():
 
302
            settings.setValue("size", self.size())
 
303
        settings.setValue('state', self.saveState())
 
304
        settings.setValue('tabbar', self.tabBar.isVisible())
 
305
        
 
306
    def readSessionSettings(self, settings):
 
307
        """Restore ourselves from session manager settings.
 
308
        
 
309
        These methods store much more information than the readSettings and
 
310
        writeSettings methods. This method tries to restore window size and
 
311
        position. Also the objectName() is set, so that the window manager can
 
312
        preserve stacking order, etc.
 
313
        
 
314
        """
 
315
        name = settings.value('name', '')
 
316
        if name:
 
317
            self.setObjectName(name)
 
318
        self.restoreGeometry(settings.value('geometry', QByteArray()))
 
319
        self.restoreState(settings.value('state', QByteArray()))
 
320
        
 
321
    def writeSessionSettings(self, settings):
 
322
        """Write our state to the session manager settings.
 
323
        
 
324
        See readSessionSettings().
 
325
        
 
326
        """
 
327
        settings.setValue('name', self.objectName())
 
328
        settings.setValue('geometry', self.saveGeometry())
 
329
        settings.setValue('state', self.saveState())
 
330
 
 
331
    def openUrl(self, url, encoding=None):
 
332
        """Same as app.openUrl but with some error checking and recent files."""
 
333
        if not url.toLocalFile():
 
334
            # we only support local files
 
335
            QMessageBox.warning(self, app.caption(_("Warning")),
 
336
                _("Can't load non-local document:\n\n{url}").format(
 
337
                    url=url.toString()))
 
338
        else:
 
339
            recentfiles.add(url)
 
340
        return app.openUrl(url, encoding)
 
341
    
 
342
    ##
 
343
    # Implementations of menu actions
 
344
    ##
 
345
    
 
346
    def newDocument(self):
 
347
        """ Creates a new, empty document. """
 
348
        self.setCurrentDocument(document.Document())
 
349
        
 
350
    def openDocument(self):
 
351
        """ Displays an open dialog to open one or more documents. """
 
352
        ext = os.path.splitext(self.currentDocument().url().path())[1]
 
353
        filetypes = app.filetypes(ext)
 
354
        caption = app.caption(_("dialog title", "Open File"))
 
355
        directory = os.path.dirname(self.currentDocument().url().toLocalFile()) or app.basedir()
 
356
        files = QFileDialog.getOpenFileNames(self, caption, directory, filetypes)
 
357
        docs = [self.openUrl(QUrl.fromLocalFile(f)) for f in files]
 
358
        if docs:
 
359
            self.setCurrentDocument(docs[-1])
 
360
        
 
361
    def saveDocument(self, doc):
 
362
        """ Saves the document, asking for a name if necessary.
 
363
        
 
364
        Returns True if saving succeeded.
 
365
        
 
366
        """
 
367
        if doc.url().isEmpty():
 
368
            return self.saveDocumentAs(doc)
 
369
        filename = dest = doc.url().toLocalFile()
 
370
        if not filename:
 
371
            dest = doc.url().toString()
 
372
        if not util.iswritable(filename):
 
373
            QMessageBox.warning(self, app.caption(_("Error")),
 
374
                _("Can't write to destination:\n\n{url}").format(url=dest))
 
375
            return False
 
376
        b = backup.backup(filename)
 
377
        success = doc.save()
 
378
        if not success:
 
379
            QMessageBox.warning(self, app.caption(_("Error")),
 
380
                _("Can't write to destination:\n\n{url}").format(url=filename))
 
381
        elif b:
 
382
            backup.removeBackup(filename)
 
383
        return success
 
384
            
 
385
    def saveDocumentAs(self, doc):
 
386
        """ Saves the document, always asking for a name.
 
387
        
 
388
        Returns True if saving succeeded.
 
389
        
 
390
        """
 
391
        filename = doc.url().toLocalFile()
 
392
        if filename:
 
393
            filetypes = app.filetypes(os.path.splitext(filename)[1])
 
394
        else:
 
395
            filename = app.basedir() # default directory to save to
 
396
            import documentinfo
 
397
            import ly.lex
 
398
            filetypes = app.filetypes(ly.lex.extensions[documentinfo.mode(doc)])
 
399
        caption = app.caption(_("dialog title", "Save File"))
 
400
        filename = QFileDialog.getSaveFileName(self, caption, filename, filetypes)
 
401
        if not filename:
 
402
            return False # cancelled
 
403
        if not util.iswritable(filename):
 
404
            QMessageBox.warning(self, app.caption(_("Error")),
 
405
                _("Can't write to destination:\n\n{url}").format(url=filename))
 
406
            return False
 
407
        url = QUrl.fromLocalFile(filename)
 
408
        doc.setUrl(url)
 
409
        recentfiles.add(url)
 
410
        return self.saveDocument(doc)
 
411
        
 
412
    def closeDocument(self, doc):
 
413
        """ Closes the document, asking for saving if modified.
 
414
        
 
415
        Returns True if closing succeeded.
 
416
        
 
417
        """
 
418
        close = self.queryCloseDocument(doc)
 
419
        if close:
 
420
            doc.close()
 
421
            # keep one document
 
422
            if not app.documents:
 
423
                self.setCurrentDocument(document.Document())
 
424
        return close
 
425
        
 
426
    def saveCurrentDocument(self):
 
427
        return self.saveDocument(self.currentDocument())
 
428
    
 
429
    def saveCurrentDocumentAs(self):
 
430
        return self.saveDocumentAs(self.currentDocument())
 
431
    
 
432
    def saveCopyAs(self):
 
433
        import ly.lex
 
434
        doc = self.currentDocument()
 
435
        if not self.currentView().textCursor().hasSelection():
 
436
            import documentinfo
 
437
            mode = documentinfo.mode(doc)
 
438
            data = doc.encodedText()
 
439
            caption = app.caption(_("dialog title", "Save Copy"))
 
440
        else:
 
441
            import fileinfo
 
442
            text = self.currentView().textCursor().selection().toPlainText()
 
443
            mode = fileinfo.textmode(text)
 
444
            data = util.encode(text)
 
445
            caption = app.caption(_("dialog title", "Save Selection"))
 
446
        filetypes = app.filetypes(ly.lex.extensions[mode])
 
447
        dirname = os.path.dirname(doc.url().toLocalFile()) or app.basedir()
 
448
        filename = QFileDialog.getSaveFileName(self, caption, dirname, filetypes)
 
449
        if not filename:
 
450
            return # cancelled
 
451
        try:
 
452
            with open(filename, "w") as f:
 
453
                f.write(data)
 
454
        except (IOError, OSError) as err:
 
455
            QMessageBox.warning(self, app.caption(_("Error")),
 
456
                _("Can't write to destination:\n\n{url}\n\n{error}").format(
 
457
                    url=filename, error=err.strerror))
 
458
    
 
459
    def closeCurrentDocument(self):
 
460
        return self.closeDocument(self.currentDocument())
 
461
    
 
462
    def saveAllDocuments(self):
 
463
        """ Saves all documents.
 
464
        
 
465
        Returns True if all documents were saved.
 
466
        If one document failed or was cancelled the whole operation is cancelled
 
467
        and this function returns False.
 
468
        
 
469
        """
 
470
        cur = self.currentDocument()
 
471
        for doc in self.historyManager.documents():
 
472
            if doc.isModified():
 
473
                if doc.url().isEmpty():
 
474
                    self.setCurrentDocument(doc, findOpenView=True)
 
475
                    res = self.saveDocumentAs(doc)
 
476
                else:
 
477
                    res = self.saveDocument(doc)
 
478
                if not res:
 
479
                    return False
 
480
        self.setCurrentDocument(cur, findOpenView=True)
 
481
        return True
 
482
                    
 
483
    def closeOtherDocuments(self):
 
484
        """ Closes all documents that are not the current document.
 
485
        
 
486
        Returns True if all documents were closed.
 
487
        
 
488
        """
 
489
        cur = self.currentDocument()
 
490
        docs = self.historyManager.documents()[1:]
 
491
        for doc in docs:
 
492
            if not self.queryCloseDocument(doc):
 
493
                self.setCurrentDocument(cur, findOpenView=True)
 
494
                return False
 
495
        for doc in docs:
 
496
            doc.close()
 
497
        return True
 
498
    
 
499
    def closeAllDocuments(self):
 
500
        """Closes all documents and keep one new, empty document."""
 
501
        sessions.manager.get(self).saveCurrentSessionIfDesired()
 
502
        if self.queryClose():
 
503
            sessions.setCurrentSession(None)
 
504
            self.setCurrentDocument(document.Document())
 
505
    
 
506
    def quit(self):
 
507
        """Closes all MainWindows."""
 
508
        for window in app.windows[:]: # copy
 
509
            if window is not self:
 
510
                window.close()
 
511
        self.close()
 
512
    
 
513
    def insertFromFile(self):
 
514
        ext = os.path.splitext(self.currentDocument().url().path())[1]
 
515
        filetypes = app.filetypes(ext)
 
516
        caption = app.caption(_("dialog title", "Insert From File"))
 
517
        directory = os.path.dirname(self.currentDocument().url().toLocalFile()) or app.basedir()
 
518
        filename = QFileDialog.getOpenFileName(self, caption, directory, filetypes)
 
519
        if filename:
 
520
            try:
 
521
                data = open(filename).read()
 
522
            except (IOError, OSError) as err:
 
523
                QMessageBox.warning(self, app.caption(_("Error")),
 
524
                    _("Can't read from source:\n\n{url}\n\n{error}").format(
 
525
                        url=filename, error=err.strerror))
 
526
            else:
 
527
                text = util.decode(data)
 
528
                self.currentView().textCursor().insertText(text)
 
529
        
 
530
    def openCurrentDirectory(self):
 
531
        import resultfiles
 
532
        directory = resultfiles.results(self.currentDocument()).currentDirectory()
 
533
        if not directory:
 
534
            directory = app.basedir() or os.getcwdu()
 
535
        helpers.openUrl(QUrl.fromLocalFile(directory), "directory")
 
536
    
 
537
    def printSource(self):
 
538
        cursor = self.currentView().textCursor()
 
539
        printer = QPrinter()
 
540
        dlg = QPrintDialog(printer, self)
 
541
        dlg.setWindowTitle(app.caption(_("dialog title", "Print Source")))
 
542
        options = QAbstractPrintDialog.PrintToFile | QAbstractPrintDialog.PrintShowPageSize
 
543
        if cursor.hasSelection():
 
544
            options |= QAbstractPrintDialog.PrintSelection
 
545
        dlg.setOptions(options)
 
546
        if dlg.exec_():
 
547
            doc = highlighter.htmlCopy(self.currentDocument(), 'printer')
 
548
            doc.setMetaInformation(QTextDocument.DocumentTitle, self.currentDocument().url().toString())
 
549
            font = doc.defaultFont()
 
550
            font.setPointSizeF(font.pointSizeF() * 0.8)
 
551
            doc.setDefaultFont(font)
 
552
            if dlg.testOption(QAbstractPrintDialog.PrintSelection):
 
553
                # cut out not selected text
 
554
                start, end = cursor.selectionStart(), cursor.selectionEnd()
 
555
                cur1 = QTextCursor(doc)
 
556
                cur1.setPosition(start, QTextCursor.KeepAnchor)
 
557
                cur2 = QTextCursor(doc)
 
558
                cur2.setPosition(end)
 
559
                cur2.movePosition(QTextCursor.End, QTextCursor.KeepAnchor)
 
560
                cur2.removeSelectedText()
 
561
                cur1.removeSelectedText()
 
562
            doc.print_(printer)
 
563
    
 
564
    def exportColoredHtml(self):
 
565
        doc = self.currentDocument()
 
566
        name, ext = os.path.splitext(os.path.basename(doc.url().path()))
 
567
        if name:
 
568
            if ext.lower() == ".html":
 
569
                name += "_html"
 
570
            name += ".html"
 
571
        dir = os.path.dirname(doc.url().toLocalFile())
 
572
        if dir:
 
573
            name = os.path.join(dir, name)
 
574
        filename = QFileDialog.getSaveFileName(self, app.caption(_("Export as HTML")),
 
575
            name, "{0} (*.html)".format("HTML Files"))
 
576
        if not filename:
 
577
            return #cancelled
 
578
        html = highlighter.htmlCopy(doc).toHtml('utf-8').encode('utf-8')
 
579
        try:
 
580
            with open(filename, "w") as f:
 
581
                f.write(str(html))
 
582
        except (IOError, OSError) as err:
 
583
            QMessageBox.warning(self, app.caption(_("Error")),
 
584
                _("Can't write to destination:\n\n{url}\n\n{error}").format(url=filename, error=err))
 
585
        
 
586
    def undo(self):
 
587
        self.currentDocument().undo()
 
588
        
 
589
    def redo(self):
 
590
        self.currentDocument().redo()
 
591
    
 
592
    def cut(self):
 
593
        self.currentView().cut()
 
594
        
 
595
    def copy(self):
 
596
        self.currentView().copy()
 
597
        
 
598
    def paste(self):
 
599
        self.currentView().paste()
 
600
        
 
601
    def copyColoredHtml(self):
 
602
        cursor = self.currentView().textCursor()
 
603
        if not cursor.hasSelection():
 
604
            return
 
605
        doc = highlighter.htmlCopy(self.currentDocument())
 
606
        cur1 = QTextCursor(doc)
 
607
        cur1.setPosition(cursor.anchor())
 
608
        cur1.setPosition(cursor.position(), QTextCursor.KeepAnchor)
 
609
        data = QMimeData()
 
610
        html = cur1.selection().toHtml()
 
611
        data.setHtml(html)
 
612
        data.setText(html)
 
613
        QApplication.clipboard().setMimeData(data)
 
614
        
 
615
    def selectNone(self):
 
616
        cursor = self.currentView().textCursor()
 
617
        cursor.clearSelection()
 
618
        self.currentView().setTextCursor(cursor)
 
619
    
 
620
    def selectAll(self):
 
621
        self.currentView().selectAll()
 
622
        
 
623
    def find(self):
 
624
        import search
 
625
        search.Search.instance(self).find()
 
626
        
 
627
    def replace(self):
 
628
        import search
 
629
        search.Search.instance(self).replace()
 
630
        
 
631
    def showPreferences(self):
 
632
        import preferences
 
633
        dlg = preferences.PreferencesDialog(self)
 
634
        dlg.exec_()
 
635
        dlg.deleteLater()
 
636
    
 
637
    def toggleFullScreen(self, enabled):
 
638
        if enabled:
 
639
            self._maximized = self.isMaximized()
 
640
            self.showFullScreen()
 
641
        else:
 
642
            self.showNormal()
 
643
            if self._maximized:
 
644
                self.showMaximized()
 
645
    
 
646
    def newWindow(self):
 
647
        """Opens a new MainWindow."""
 
648
        self.writeSettings()
 
649
        MainWindow(self).show()
 
650
 
 
651
    def scrollUp(self):
 
652
        """Scroll up without moving the cursor"""
 
653
        sb = self.currentView().verticalScrollBar()
 
654
        sb.setValue(sb.value() - 1 if sb.value() else 0)
 
655
        
 
656
    def scrollDown(self):
 
657
        """Scroll down without moving the cursor"""
 
658
        sb = self.currentView().verticalScrollBar()
 
659
        sb.setValue(sb.value() + 1)
 
660
        
 
661
    def selectFullLinesUp(self):
 
662
        """Select lines upwards, selecting full lines."""
 
663
        self.selectFullLines(QTextCursor.Up)
 
664
        
 
665
    def selectFullLinesDown(self):
 
666
        """Select lines downwards, selecting full lines."""
 
667
        self.selectFullLines(QTextCursor.Down)
 
668
        
 
669
    def selectFullLines(self, direction):
 
670
        """Select full lines in the direction QTextCursor.Up or Down."""
 
671
        view = self.currentView()
 
672
        cur = view.textCursor()
 
673
        position = cur.position()
 
674
        cur.setPosition(cur.anchor())
 
675
        cur.movePosition(QTextCursor.StartOfLine)
 
676
        cur.setPosition(position, QTextCursor.KeepAnchor)
 
677
        cur.movePosition(direction, QTextCursor.KeepAnchor)
 
678
        cur.movePosition(QTextCursor.StartOfLine, QTextCursor.KeepAnchor)
 
679
        view.setTextCursor(cur)
 
680
    
 
681
    def showManual(self):
 
682
        """Shows the user guide, called when user presses F1."""
 
683
        import help
 
684
        help.help("contents")
 
685
    
 
686
    def showAbout(self):
 
687
        """Shows about dialog."""
 
688
        import about
 
689
        about.AboutDialog(self).exec_()
 
690
    
 
691
    def reportBug(self):
 
692
        """Opens e-mail composer to send a bug or feature report."""
 
693
        import bugreport
 
694
        bugreport.email('', _(
 
695
            "Please describe the issue or feature request.\n"
 
696
            "Provide as much information as possible.\n\n\n"))
 
697
    
 
698
    def createActions(self):
 
699
        self.actionCollection = ac = ActionCollection()
 
700
        
 
701
        # recent files
 
702
        self.menu_recent_files = m = QMenu()
 
703
        ac.file_open_recent.setMenu(m)
 
704
        m.aboutToShow.connect(self.populateRecentFilesMenu)
 
705
        m.triggered.connect(self.slotRecentFilesAction)
 
706
        
 
707
        # connections
 
708
        ac.file_quit.triggered.connect(self.quit, Qt.QueuedConnection)
 
709
        ac.file_new.triggered.connect(self.newDocument)
 
710
        ac.file_open.triggered.connect(self.openDocument)
 
711
        ac.file_insert_file.triggered.connect(self.insertFromFile)
 
712
        ac.file_open_current_directory.triggered.connect(self.openCurrentDirectory)
 
713
        ac.file_save.triggered.connect(self.saveCurrentDocument)
 
714
        ac.file_save_as.triggered.connect(self.saveCurrentDocumentAs)
 
715
        ac.file_save_copy_as.triggered.connect(self.saveCopyAs)
 
716
        ac.file_save_all.triggered.connect(self.saveAllDocuments)
 
717
        ac.file_print_source.triggered.connect(self.printSource)
 
718
        ac.file_close.triggered.connect(self.closeCurrentDocument)
 
719
        ac.file_close_other.triggered.connect(self.closeOtherDocuments)
 
720
        ac.file_close_all.triggered.connect(self.closeAllDocuments)
 
721
        ac.export_colored_html.triggered.connect(self.exportColoredHtml)
 
722
        ac.edit_undo.triggered.connect(self.undo)
 
723
        ac.edit_redo.triggered.connect(self.redo)
 
724
        ac.edit_cut.triggered.connect(self.cut)
 
725
        ac.edit_copy.triggered.connect(self.copy)
 
726
        ac.edit_paste.triggered.connect(self.paste)
 
727
        ac.edit_copy_colored_html.triggered.connect(self.copyColoredHtml)
 
728
        ac.edit_select_all.triggered.connect(self.selectAll)
 
729
        ac.edit_select_none.triggered.connect(self.selectNone)
 
730
        ac.edit_select_full_lines_up.triggered.connect(self.selectFullLinesUp)
 
731
        ac.edit_select_full_lines_down.triggered.connect(self.selectFullLinesDown)
 
732
        ac.edit_find.triggered.connect(self.find)
 
733
        ac.edit_replace.triggered.connect(self.replace)
 
734
        ac.edit_preferences.triggered.connect(self.showPreferences)
 
735
        ac.view_next_document.triggered.connect(self.tabBar.nextDocument)
 
736
        ac.view_previous_document.triggered.connect(self.tabBar.previousDocument)
 
737
        ac.view_scroll_up.triggered.connect(self.scrollUp)
 
738
        ac.view_scroll_down.triggered.connect(self.scrollDown)
 
739
        ac.window_new.triggered.connect(self.newWindow)
 
740
        ac.window_fullscreen.toggled.connect(self.toggleFullScreen)
 
741
        ac.help_manual.triggered.connect(self.showManual)
 
742
        ac.help_whatsthis.triggered.connect(QWhatsThis.enterWhatsThisMode)
 
743
        ac.help_about.triggered.connect(self.showAbout)
 
744
        ac.help_bugreport.triggered.connect(self.reportBug)
 
745
        
 
746
    def populateRecentFilesMenu(self):
 
747
        self.menu_recent_files.clear()
 
748
        for url in recentfiles.urls():
 
749
            f = url.toLocalFile()
 
750
            dirname, basename = os.path.split(f)
 
751
            text = "{0}  ({1})".format(basename, util.homify(dirname))
 
752
            self.menu_recent_files.addAction(text).url = url
 
753
        util.addAccelerators(self.menu_recent_files.actions())
 
754
    
 
755
    def slotRecentFilesAction(self, action):
 
756
        """Called when a recent files menu action is triggered."""
 
757
        doc = self.openUrl(action.url)
 
758
        self.setCurrentDocument(doc)
 
759
        
 
760
    def createMenus(self):
 
761
        menu.createMenus(self)
 
762
        # actions that are not in menus
 
763
        ac = self.actionCollection
 
764
        self.addAction(ac.view_scroll_up)
 
765
        self.addAction(ac.view_scroll_down)
 
766
        self.addAction(ac.edit_select_full_lines_up)
 
767
        self.addAction(ac.edit_select_full_lines_down)
 
768
        
 
769
    def createToolBars(self):
 
770
        ac = self.actionCollection
 
771
        self.toolbar_main = t = self.addToolBar('')
 
772
        t.setObjectName('toolbar_main')
 
773
        t.addAction(ac.file_new)
 
774
        t.addAction(ac.file_open)
 
775
        t.addSeparator()
 
776
        t.addAction(ac.file_save)
 
777
        t.addAction(ac.file_save_as)
 
778
        t.addSeparator()
 
779
        t.addAction(ac.edit_undo)
 
780
        t.addAction(ac.edit_redo)
 
781
        t.addSeparator()
 
782
        t.addAction(scorewiz.ScoreWizard.instance(self).actionCollection.scorewiz)
 
783
        t.addAction(engrave.engraver(self).actionCollection.engrave_runner)
 
784
        
 
785
        self.toolbar_music = t = self.addToolBar('')
 
786
        t.setObjectName('toolbar_music')
 
787
        ma = panels.manager(self).musicview.actionCollection
 
788
        t.addAction(ma.music_document_select)
 
789
        t.addAction(ma.music_print)
 
790
        t.addSeparator()
 
791
        t.addAction(ma.music_zoom_in)
 
792
        t.addAction(ma.music_zoom_combo)
 
793
        t.addAction(ma.music_zoom_out)
 
794
        
 
795
    def translateUI(self):
 
796
        self.toolbar_main.setWindowTitle(_("Main Toolbar"))
 
797
        self.toolbar_music.setWindowTitle(_("Music View Toolbar"))
 
798
 
 
799
 
 
800
class ActionCollection(actioncollection.ActionCollection):
 
801
    name = "main"
 
802
    def createActions(self, parent=None):
 
803
        self.file_new = QAction(parent)
 
804
        self.file_open = QAction(parent)
 
805
        self.file_open_recent = QAction(parent)
 
806
        self.file_insert_file = QAction(parent)
 
807
        self.file_open_current_directory = QAction(parent)
 
808
        self.file_save = QAction(parent)
 
809
        self.file_save_as = QAction(parent)
 
810
        self.file_save_copy_as = QAction(parent)
 
811
        self.file_save_all = QAction(parent)
 
812
        self.file_print_source = QAction(parent)
 
813
        self.file_close = QAction(parent)
 
814
        self.file_close_other = QAction(parent)
 
815
        self.file_close_all = QAction(parent)
 
816
        self.file_quit = QAction(parent)
 
817
        
 
818
        self.export_colored_html = QAction(parent)
 
819
        
 
820
        self.edit_undo = QAction(parent)
 
821
        self.edit_redo = QAction(parent)
 
822
        self.edit_cut = QAction(parent)
 
823
        self.edit_copy = QAction(parent)
 
824
        self.edit_copy_colored_html = QAction(parent)
 
825
        self.edit_paste = QAction(parent)
 
826
        self.edit_select_all = QAction(parent)
 
827
        self.edit_select_current_toplevel = QAction(parent)
 
828
        self.edit_select_none = QAction(parent)
 
829
        self.edit_select_full_lines_up = QAction(parent)
 
830
        self.edit_select_full_lines_down = QAction(parent)
 
831
        self.edit_find = QAction(parent)
 
832
        self.edit_find_next = QAction(parent)
 
833
        self.edit_find_previous = QAction(parent)
 
834
        self.edit_replace = QAction(parent)
 
835
        self.edit_preferences = QAction(parent)
 
836
        
 
837
        self.view_next_document = QAction(parent)
 
838
        self.view_previous_document = QAction(parent)
 
839
        self.view_scroll_up = QAction(parent)
 
840
        self.view_scroll_down = QAction(parent)
 
841
        
 
842
        self.window_new = QAction(parent)
 
843
        self.window_fullscreen = QAction(parent)
 
844
        self.window_fullscreen.setCheckable(True)
 
845
        
 
846
        self.help_manual = QAction(parent)
 
847
        self.help_whatsthis = QAction(parent)
 
848
        self.help_about = QAction(parent)
 
849
        self.help_bugreport = QAction(parent)
 
850
        
 
851
        # icons
 
852
        self.file_new.setIcon(icons.get('document-new'))
 
853
        self.file_open.setIcon(icons.get('document-open'))
 
854
        self.file_open_recent.setIcon(icons.get('document-open-recent'))
 
855
        self.file_open_current_directory.setIcon(icons.get('folder-open'))
 
856
        self.file_save.setIcon(icons.get('document-save'))
 
857
        self.file_save_as.setIcon(icons.get('document-save-as'))
 
858
        self.file_save_copy_as.setIcon(icons.get('document-save-as'))
 
859
        self.file_save_all.setIcon(icons.get('document-save-all'))
 
860
        self.file_print_source.setIcon(icons.get('document-print'))
 
861
        self.file_close.setIcon(icons.get('document-close'))
 
862
        self.file_quit.setIcon(icons.get('application-exit'))
 
863
        
 
864
        self.edit_undo.setIcon(icons.get('edit-undo'))
 
865
        self.edit_redo.setIcon(icons.get('edit-redo'))
 
866
        self.edit_cut.setIcon(icons.get('edit-cut'))
 
867
        self.edit_copy.setIcon(icons.get('edit-copy'))
 
868
        self.edit_paste.setIcon(icons.get('edit-paste'))
 
869
        self.edit_select_all.setIcon(icons.get('edit-select-all'))
 
870
        self.edit_select_current_toplevel.setIcon(icons.get('edit-select'))
 
871
        self.edit_find.setIcon(icons.get('edit-find'))
 
872
        self.edit_find_next.setIcon(icons.get('go-down-search'))
 
873
        self.edit_find_previous.setIcon(icons.get('go-up-search'))
 
874
        self.edit_replace.setIcon(icons.get('edit-find-replace'))
 
875
        self.edit_preferences.setIcon(icons.get('preferences-system'))
 
876
        
 
877
        self.view_next_document.setIcon(icons.get('go-next'))
 
878
        self.view_previous_document.setIcon(icons.get('go-previous'))
 
879
        
 
880
        self.window_new.setIcon(icons.get('window-new'))
 
881
        self.window_fullscreen.setIcon(icons.get('view-fullscreen'))
 
882
        
 
883
        self.help_manual.setIcon(icons.get('help-contents'))
 
884
        self.help_whatsthis.setIcon(icons.get('help-contextual'))
 
885
        self.help_bugreport.setIcon(icons.get('tools-report-bug'))
 
886
        self.help_about.setIcon(icons.get('help-about'))
 
887
        
 
888
        # shortcuts
 
889
        self.file_new.setShortcuts(QKeySequence.New)
 
890
        self.file_open.setShortcuts(QKeySequence.Open)
 
891
        self.file_save.setShortcuts(QKeySequence.Save)
 
892
        self.file_save_as.setShortcuts(QKeySequence.SaveAs)
 
893
        self.file_close.setShortcuts(QKeySequence.Close)
 
894
        self.file_quit.setShortcuts(QKeySequence.Quit)
 
895
        
 
896
        self.edit_undo.setShortcuts(QKeySequence.Undo)
 
897
        self.edit_redo.setShortcuts(QKeySequence.Redo)
 
898
        self.edit_cut.setShortcuts(QKeySequence.Cut)
 
899
        self.edit_copy.setShortcuts(QKeySequence.Copy)
 
900
        self.edit_paste.setShortcuts(QKeySequence.Paste)
 
901
        self.edit_select_all.setShortcuts(QKeySequence.SelectAll)
 
902
        self.edit_select_current_toplevel.setShortcut(QKeySequence(Qt.SHIFT+Qt.CTRL+Qt.Key_B))
 
903
        self.edit_select_none.setShortcut(QKeySequence(Qt.SHIFT + Qt.CTRL + Qt.Key_A))
 
904
        self.edit_select_full_lines_up.setShortcut(QKeySequence(Qt.SHIFT + Qt.CTRL + Qt.Key_Up))
 
905
        self.edit_select_full_lines_down.setShortcut(QKeySequence(Qt.SHIFT + Qt.CTRL + Qt.Key_Down))
 
906
        self.edit_find.setShortcuts(QKeySequence.Find)
 
907
        self.edit_find_next.setShortcuts(QKeySequence.FindNext)
 
908
        self.edit_find_previous.setShortcuts(QKeySequence.FindPrevious)
 
909
        self.edit_replace.setShortcuts(QKeySequence.Replace)
 
910
        self.edit_preferences.setShortcuts(QKeySequence.Preferences)
 
911
        
 
912
        self.view_next_document.setShortcuts(QKeySequence.Forward)
 
913
        self.view_previous_document.setShortcuts(QKeySequence.Back)
 
914
        self.view_scroll_up.setShortcut(Qt.CTRL + Qt.Key_Up)
 
915
        self.view_scroll_down.setShortcut(Qt.CTRL + Qt.Key_Down)
 
916
        
 
917
        self.window_fullscreen.setShortcuts([QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_F), QKeySequence(Qt.Key_F11)])
 
918
        
 
919
        self.help_manual.setShortcuts(QKeySequence.HelpContents)
 
920
        self.help_whatsthis.setShortcuts(QKeySequence.WhatsThis)
 
921
        
 
922
    def translateUI(self):
 
923
        self.file_new.setText(_("action: new document", "&New"))
 
924
        self.file_open.setText(_("&Open..."))
 
925
        self.file_open_recent.setText(_("Open &Recent"))
 
926
        self.file_insert_file.setText(_("Insert from &File..."))
 
927
        self.file_open_current_directory.setText(_("Open Current Directory"))
 
928
        self.file_save.setText(_("&Save"))
 
929
        self.file_save_as.setText(_("Save &As..."))
 
930
        self.file_save_copy_as.setText(_("Save Copy or Selection As..."))
 
931
        self.file_save_all.setText(_("Save All"))
 
932
        self.file_print_source.setText(_("Print Source..."))
 
933
        self.file_close.setText(_("&Close"))
 
934
        self.file_close_other.setText(_("Close Other Documents"))
 
935
        self.file_close_all.setText(_("Close All Documents"))
 
936
        self.file_close_all.setToolTip(_("Closes all documents and leaves the current session."))
 
937
        self.file_quit.setText(_("&Quit"))
 
938
        
 
939
        self.export_colored_html.setText(_("Export Source as Colored &HTML..."))
 
940
        
 
941
        self.edit_undo.setText(_("&Undo"))
 
942
        self.edit_redo.setText(_("Re&do"))
 
943
        self.edit_cut.setText(_("Cu&t"))
 
944
        self.edit_copy.setText(_("&Copy"))
 
945
        self.edit_copy_colored_html.setText(_("Copy as Colored &HTML"))
 
946
        self.edit_paste.setText(_("&Paste"))
 
947
        self.edit_select_all.setText(_("Select &All"))
 
948
        self.edit_select_current_toplevel.setText(_("Select &Block"))
 
949
        self.edit_select_none.setText(_("Select &None"))
 
950
        self.edit_select_full_lines_up.setText(_("Select Whole Lines Up"))
 
951
        self.edit_select_full_lines_down.setText(_("Select Whole Lines Down"))
 
952
        self.edit_find.setText(_("&Find..."))
 
953
        self.edit_find_next.setText(_("Find Ne&xt"))
 
954
        self.edit_find_previous.setText(_("Find Pre&vious"))
 
955
        self.edit_replace.setText(_("&Replace..."))
 
956
        self.edit_preferences.setText(_("Pr&eferences..."))
 
957
        
 
958
        self.view_next_document.setText(_("&Next Document"))
 
959
        self.view_previous_document.setText(_("&Previous Document"))
 
960
        self.view_scroll_up.setText(_("Scroll Up"))
 
961
        self.view_scroll_down.setText(_("Scroll Down"))
 
962
        
 
963
        self.window_new.setText(_("New &Window"))
 
964
        self.window_fullscreen.setText(_("&Fullscreen"))
 
965
        
 
966
        self.help_manual.setText(_("&User Guide"))
 
967
        self.help_whatsthis.setText(_("&What's This?"))
 
968
        self.help_bugreport.setText(_("Report a &Bug..."))
 
969
        self.help_about.setText(_("&About {appname}...").format(appname=info.appname))
 
970
        
 
971