source: trunk/tools/writing_editor/scripts/writingEditor.py @ 280

Revision 280, 21.6 KB checked in by bretzel_parpg, 10 years ago (diff)

Patch by Bretzel.

  • Wrote a parser for the new syntax I developed (I will post about this at the forums in the Dialog System Implementation thread)
  • Started to add a dialogue map, which will show you the flow of the dialogue
  • Overrode the resize event in the application so that it will also resize the editor and dialog map
Line 
1#!/usr/bin/python
2
3#   This file is part of PARPG.
4
5#   PARPG is free software: you can redistribute it and/or modify
6#   it under the terms of the GNU General Public License as published by
7#   the Free Software Foundation, either version 3 of the License, or
8#   (at your option) any later version.
9
10#   PARPG 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 PARPG.  If not, see <http://www.gnu.org/licenses/>.
17
18import sys, os, codecs
19
20from PyQt4 import QtGui, QtCore
21from ui.editor_ui import Ui_writingEditor
22from ui.popupWindows import *
23from scripts.syntaxHighlight import SyntaxHighlighter
24from scripts.settings import Settings
25from scripts.dialogMap import DialogMap
26from scripts.parser import parse
27
28
29class WritingEditor(QtGui.QMainWindow):
30    """
31    The main class for the writing editor
32    """
33    def __init__(self, parent=None):
34        """
35        Initialize the editor
36        """
37        QtGui.QWidget.__init__(self, parent)
38       
39        self.settings = Settings()
40        self.settings.readSettingsFromFile('data/options.txt')
41        self.resize(int(self.settings.res_width), int(self.settings.res_height))
42
43        self.ui = Ui_writingEditor()
44        self.ui.setupUi(self)
45        self.ui.dialog_map = DialogMap(self.settings, self.ui.main_edit, self.ui.dialog_map_tab)
46        self.syntax = SyntaxHighlighter(self.ui.main_edit.document())
47        self.syntaxCreated = True
48        self.changes = False
49        self.connectSignals()
50        self.setupMenus()
51
52        self.open_file_name = None
53        self.saveEnabled(False)
54        self.title_asterisk = False
55        self.ui.actionNone.setEnabled(False)
56        self.getRecentItems('data/recent_files.txt')
57
58
59    def setupMenus(self):
60        """
61        Setup the menus:
62        Add the shortcuts to all the menu's and also create the shorcuts
63        Add the images for the icons
64        @return: None
65        """
66        self.ui.actionNew_File.setText('&New File\tCtrl+N')
67        self.ui.actionOpen_File.setText('&Open File\tCtrl+O')
68        self.ui.actionSave.setText('&Save\tCtrl+S')
69        self.ui.actionSave_As.setText('Save &As\tCtrl+Shift+S')
70        self.ui.actionPrint.setText('&Print\tCtrl+P')
71        self.ui.actionExit.setText('&Exit\tCtrl+Q')
72        self.ui.actionUndo.setText('&Undo\tCtrl+Z')
73        self.ui.actionRedo.setText('&Redo\tCtrl+Y')
74        self.ui.actionCopy.setText('&Copy\tCtrl+C')
75        self.ui.actionCut.setText('C&ut\tCtrl+X')
76        self.ui.actionPaste.setText('&Paste\tCtrl+V')
77        self.ui.actionPreferences.setText('P&references\tCtrl+Shift+P')
78        self.ui.actionHelp.setText("&Help\tF1")
79        self.ui.actionAbout.setText('&About\tF2')
80        self.ui.actionNone.setText('None')
81
82        self.ui.actionNew_File.setShortcut(QtGui.QKeySequence('Ctrl+N'))
83        self.ui.actionOpen_File.setShortcut(QtGui.QKeySequence('Ctrl+O'))
84        self.ui.actionSave.setShortcut(QtGui.QKeySequence('Ctrl+S'))
85        self.ui.actionSave_As.setShortcut(QtGui.QKeySequence('Ctrl+Shift+S'))
86        self.ui.actionPrint.setShortcut(QtGui.QKeySequence('Ctrl+P'))
87        self.ui.actionExit.setShortcut(QtGui.QKeySequence('Ctrl+Q'))
88        self.ui.actionUndo.setShortcut(QtGui.QKeySequence('Ctrl+Z'))
89        self.ui.actionRedo.setShortcut(QtGui.QKeySequence('Ctrl+Y'))
90        self.ui.actionCopy.setShortcut(QtGui.QKeySequence('Ctrl+C'))
91        self.ui.actionCut.setShortcut(QtGui.QKeySequence('Ctrl+X'))
92        self.ui.actionPaste.setShortcut(QtGui.QKeySequence('Ctrl+V'))
93        self.ui.actionPreferences.setShortcut(QtGui.QKeySequence('Ctrl+Shift+P'))
94        self.ui.actionHelp.setShortcut(QtGui.QKeySequence('F1'))
95        self.ui.actionAbout.setShortcut(QtGui.QKeySequence('F2'))
96
97        self.ui.actionNew_File.setIcon(self.createIcon('new.png'))
98        self.ui.actionOpen_File.setIcon(self.createIcon('open.png'))
99        self.ui.actionSave.setIcon(self.createIcon('save.png'))
100        self.ui.actionSave_As.setIcon(self.createIcon('save_as.png'))
101        self.ui.actionPrint.setIcon(self.createIcon('printer.png'))
102        self.ui.actionExit.setIcon(self.createIcon('close.png'))
103        self.ui.actionUndo.setIcon(self.createIcon('undo.png'))
104        self.ui.actionRedo.setIcon(self.createIcon('redo.png'))
105        self.ui.actionCopy.setIcon(self.createIcon('copy.png'))
106        self.ui.actionCut.setIcon(self.createIcon('cut.png'))
107        self.ui.actionPaste.setIcon(self.createIcon('paste.png'))
108        self.ui.actionPreferences.setIcon(self.createIcon('preferences.png'))
109        self.ui.actionHelp.setIcon(self.createIcon('help.png'))
110        self.ui.actionAbout.setIcon(self.createIcon('about.png'))
111
112        self.ui.actionNew_File.setStatusTip('Create a new file')
113        self.ui.actionOpen_File.setStatusTip('Open a file')
114        self.ui.actionSave.setStatusTip('Save the open file to disk')
115        self.ui.actionSave_As.setStatusTip('Save the contents of the open file to a new file on the disk')
116        self.ui.actionPrint.setStatusTip('Print the open file')
117        self.ui.actionExit.setStatusTip('Exit the editor')
118        self.ui.actionUndo.setStatusTip('Undo the last action within the text editor')
119        self.ui.actionRedo.setStatusTip('Redo the last action within the text editor')
120        self.ui.actionCopy.setStatusTip('Copy the selected text to the clipboard')
121        self.ui.actionCut.setStatusTip('Delete the selected text and copy it to the clipboard')
122        self.ui.actionPaste.setStatusTip('Paste the text on the clipboard')
123        self.ui.actionPreferences.setStatusTip('Edit preferences with the editor')
124        self.ui.actionHelp.setStatusTip('Help with the editor and scripting language itself')
125        self.ui.actionAbout.setStatusTip('About the editor')
126        self.ui.actionNone.setStatusTip('There are no recent files')
127
128    def connectSignals(self):
129        """
130        Connect all the buttons, widgets, etc to their respective functions
131        @return: None
132        """
133        QtCore.QObject.connect(self.ui.main_tabs, QtCore.SIGNAL("currentChanged(int)"),
134                               self.onTabChanged)
135        QtCore.QObject.connect(self.ui.main_edit, QtCore.SIGNAL("textChanged()"),
136                               self.onTextChanged)
137
138        QtCore.QObject.connect(self.ui.actionNew_File, QtCore.SIGNAL("triggered()"),
139                               self.newFile)
140        QtCore.QObject.connect(self.ui.actionOpen_File, QtCore.SIGNAL("triggered()"),
141                               self.openFile)
142        QtCore.QObject.connect(self.ui.actionSave, QtCore.SIGNAL("triggered()"),
143                               self.saveFile)
144        QtCore.QObject.connect(self.ui.actionPrint, QtCore.SIGNAL("triggered()"),
145                               self.printFile)
146        QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL("triggered()"),
147                               lambda: self.quit('data/recent_files.txt'))
148
149        QtCore.QObject.connect(self.ui.actionCopy, QtCore.SIGNAL("triggered()"),
150                               self.ui.main_edit.copy)
151        QtCore.QObject.connect(self.ui.actionCut, QtCore.SIGNAL("triggered()"),
152                               self.ui.main_edit.cut)
153        QtCore.QObject.connect(self.ui.actionPaste, QtCore.SIGNAL("triggered()"),
154                               self.ui.main_edit.paste)
155        QtCore.QObject.connect(self.ui.actionRedo, QtCore.SIGNAL("triggered()"),
156                               self.ui.main_edit.redo)
157        QtCore.QObject.connect(self.ui.actionUndo, QtCore.SIGNAL("triggered()"),
158                               self.ui.main_edit.undo)
159        QtCore.QObject.connect(self.ui.actionPreferences, QtCore.SIGNAL("triggered()"),
160                               self.createPrefWindow)
161
162        QtCore.QObject.connect(self.ui.actionAbout, QtCore.SIGNAL("triggered()"),
163                               self.createAboutWindow)
164        QtCore.QObject.connect(self.ui.actionHelp, QtCore.SIGNAL("triggered()"),
165                               self.createHelpWindow)
166
167    def onTextChanged(self):
168        """
169        Function called when text is changed
170        """
171        if (self.syntaxCreated):
172            self.syntaxCreated = False
173
174        else:
175            self.saveEnabled(True)
176            self.changes = True
177            if (self.windowTitle() == "PARPG Writing Editor - Untitled"):
178                return
179
180            if (self.open_file_name == None):
181                self.setWindowTitle("PARPG Writing Editor - Untitled")
182                return
183
184            if (self.title_asterisk == False):
185                self.setWindowTitle(self.windowTitle() + " *")
186                self.title_asterisk = True
187           
188    def onTabChanged(self, index):
189        """
190        Check if the tab is the editor or the map viewer and disable/enable actions
191        accordingly
192        @type index: int
193        @param index: The index of the tab
194        @return: None
195        """
196        # it's the main editor
197        if (index == 0):
198            self.ui.actionCopy.setEnabled(True)
199            self.ui.actionCut.setEnabled(True)
200            self.ui.actionPaste.setEnabled(True)
201        # it's the dialog map
202        elif (index == 1):
203            self.ui.dialog_map.map.refreshMap()
204            self.ui.actionCopy.setEnabled(False)
205            self.ui.actionCut.setEnabled(False)
206            self.ui.actionPaste.setEnabled(False)
207        else:
208            print 'Parameter index should be either 0 or 1. Got %d' % index
209
210    def createIcon(self, name):
211        """
212        Creates a QIcon object from the path name
213        @type name: string
214        @param name: the name of the file
215        @rtype: QtGui.QIcon
216        @return: The QIcon object
217        """
218        path = 'data/images/' + name
219        icon = QtGui.QIcon(path)
220        return icon
221
222    def newFile(self):
223        """
224        Start a new file
225        @return: None
226        """
227        self.open_file_name = None
228        self.ui.main_edit.setText("")
229        self.saveEnabled(False)
230
231    def saveFile(self, filename=None):
232        """
233        Save the contents of self.ui.main_edit to filename
234        If filename is None, then open a save dialog
235        @type filename: string
236        @param filename: the file to save to
237        @return: None
238        """
239        # if no filename argument is specified and there is no open file, open the save dialog
240        if (filename == None and self.open_file_name == None):
241            file_dialog = QtGui.QFileDialog(self)
242            file_dialog.setDefaultSuffix("dialog")
243            file_dialog.setNameFilter("Dialog Files (*.dialog)")
244            self.save_file_name = file_dialog.getSaveFileName()
245            self.open_file_name = self.save_file_name
246        # otherwise just save the file
247        else:
248            self.save_file_name = self.open_file_name
249
250        try:
251            text = self.ui.main_edit.toPlainText()
252            codec_file = codecs.open(self.save_file_name, 'w', 'utf-8')
253            codec_file.write(text)
254            codec_file.close()
255            self.saveEnabled(False)
256            last_slash = self.findLastSlash(self.save_file_name)
257            self.setWindowTitle("PARPG Writing Editor - " + self.save_file_name[last_slash+1:])
258            self.title_asterisk = False
259           
260        except IOError:
261            print 'Unable to save to file: %s' % self.save_file_name
262
263    def saveAs(self):
264        """
265        Open a dialog to save the current file as a new file
266        @return: None
267        """
268        self.saveFile()
269
270    def openFile(self, filename=None):
271        """
272        Open a file
273        @type filename: String
274        @param filename: the file to open
275        @return: None
276        """
277        old_file_name = self.open_file_name
278        if (filename == None):
279            file_dialog = QtGui.QFileDialog(self)
280            self.open_file_name = file_dialog.getOpenFileName()
281        else:
282            self.open_file_name = filename
283        try:
284            codec_file = codecs.open(self.open_file_name, 'r', 'utf-8')
285            codec_contents = codec_file.read()
286            self.ui.main_edit.setText(codec_contents)
287            self.saveEnabled(False)
288
289            new_dict = {}
290
291            try:
292                recent_length = len(self.recent_items)
293                keys = self.recent_items.keys()
294                if (recent_length != 0):
295                    keys.remove(keys[recent_length-1])
296                for key in keys:
297                    value = self.recent_items[key]
298                    new_dict[key] = value
299            except:
300                recent_length = 0
301               
302            last_slash = self.findLastSlash(self.open_file_name)
303            before = self.open_file_name[:last_slash+1]
304            after = self.open_file_name[last_slash+1:]
305            new_dict[after] = before
306               
307            self.recent_items = new_dict
308            self.updateRecentItems()
309
310            slash = self.findLastSlash(self.open_file_name)
311            window_title = 'PARPG Writing Editor - ' + self.open_file_name[slash+1:]
312            self.ui.writingEditor.setWindowTitle(window_title)
313            self.title_asterisk = False
314
315        except IOError:
316            print 'Unable to open file: %s' % self.open_file_name
317            self.open_file_name = old_file_name           
318
319    def printFile(self):
320        """
321        Print the currently open file
322        """
323        qprinter = QtGui.QPrinter()
324        print_dialog = PrintDialog(qprinter)
325        ret = print_dialog.run()
326        if (ret == QtGui.QDialog.Accepted):
327            self.ui.main_edit.document().print_(qprinter)
328
329    def saveEnabled(self, value):
330        """
331        Change whether save is enabled
332        @type value: bool
333        @param value: whether to enable or disable save
334        @return: None
335        """
336        self.ui.actionSave.setEnabled(value)
337        self.ui.actionSave_As.setEnabled(value)
338
339    def createAboutWindow(self):
340        """
341        Create the about the program window
342        @return: None
343        """
344        if (not hasattr(self, "about_window")):
345            self.about_window = AboutWindow(self)
346        self.about_window.show()
347
348    def createPrefWindow(self):
349        """
350        Create the preferences window
351        @return: None
352        """
353        if (not hasattr(self, "pref_window")):
354            self.pref_window = PrefWindow(self, self.settings)
355        self.pref_window.show()
356        self.pref_window.button_apply.setEnabled(True)
357
358    def createHelpWindow(self):
359        """
360        Create the help window
361        @return: None
362        """
363        if (not hasattr(self, "help_window")):
364            self.help_window = HelpWindow(self.settings, self)
365        self.help_window.show()
366
367    def getRecentItems(self, filename):
368        """
369        Reads all the filenames from the file filename that contains all the recent files
370        @type filename: string
371        @param filename: the path to the file
372        @return: None
373        """
374        self.recent_items = {}
375        try:
376            recent_files = open(filename, 'r').read().strip()
377           
378            if (recent_files == ""):
379                self.recent_items = None
380                return
381
382            recent_list = recent_files.split('\n')
383
384            for item in recent_list:
385                last_slash = self.findLastSlash(item)                       
386                before = item[:last_slash+1]
387                after = item[last_slash+1:]
388                self.recent_items[after] = before
389
390            self.updateRecentItems()
391            self.ui.menuRecent_Files.removeAction(self.ui.actionNone)
392
393        except IOError:
394            print 'Unable to read the recent files from file: %s\n'\
395                'No recent files will be displayed' % str(filename)
396           
397
398    def updateRecentItems(self):
399        """
400        Make the recent items show up in the gui
401        @return: None
402        """
403        try:
404            self.ui.menuRecent_files.removeAction(self.recent_1)
405        except:
406            print "Cannot remove action self.recent_1"
407        try:
408            self.ui.menuRecent_files.removeAction(self.recent_2)
409        except:
410            print "Cannot remove action self.recent_2"
411        try:
412            self.ui.menuRecent_files.removeAction(self.recent_3)
413        except:
414            print "Cannot remove action self.recent_3"
415        try:
416            self.ui.menuRecent_files.removeAction(self.recent_4)
417        except:
418            print "Cannot remove action self.recent_4"
419
420        recent_keys = []
421        for key in self.recent_items:
422            recent_keys.append(key)
423           
424        try:
425            self.recent_1 = QtGui.QAction(self)
426            self.recent_1.setObjectName(recent_keys[0])
427            self.recent_1.setText(recent_keys[0])
428            self.ui.menuRecent_Files.addAction(self.recent_1)
429            full_path_1 = self.recent_items[recent_keys[0]] + recent_keys[0]
430            QtCore.QObject.connect(self.recent_1, QtCore.SIGNAL('triggered()'),
431                                   lambda: self.openFile(full_path_1))
432           
433        except:
434            print 'Error generating widgets for recent item 1'
435           
436        try:
437            self.recent_2 = QtGui.QAction(self)
438            self.recent_2.setObjectName(recent_keys[1])
439            self.recent_2.setText(recent_keys[1])
440            self.ui.menuRecent_Files.addAction(self.recent_2, 0)
441            full_path_2 = self.recent_items[recent_keys[1]] + recent_keys[1]
442            QtCore.QObject.connect(self.recent_2, QtCore.SIGNAL('triggered()'),
443                                   lambda: self.openFile(full_path_2))
444                   
445        except:
446            print 'Error generating widgets for recent item 2'
447           
448        try:
449            self.recent_3 = QtGui.QAction(self)
450            self.recent_3.setObjectName(recent_keys[2])
451            self.recent_3.setText(recent_keys[2])
452            self.ui.menuRecent_Files.addAction(self.recent_3)
453            full_path_3 = self.recent_items[recent_keys[2]] + recent_keys[2]
454            QtCore.QObject.connect(self.recent_3, QtCore.SIGNAL('triggered()'),
455                                   lambda: self.openFile(full_path_3))
456       
457        except:
458            print 'Error generating widgets for recent item 3'
459
460        try:
461            self.recent_4 = QtGui.QAction(self)
462            self.recent_4.setObjectName(recent_keys[3])
463            self.recent_4.setText(recent_keys[3])
464            self.ui.menuRecent_Files.addAction(self.recent_4)
465            full_path_4 = self.recent_items[recent_keys[3]] + recent_keys[3]
466            QtCore.QObject.connect(self.recent_4, QtCore.SIGNAL('triggered()'),
467                                   lambda: self.openFile(full_path_4))
468       
469        except:
470            print 'Error generating widgets for recent item 4'
471           
472
473    def writeRecentItems(self, filename):
474        """
475        Write the recent items to the file filename
476        @type filename: string
477        @param filename: the file to write to
478        @return: None
479        """
480        if self.recent_items == None:
481            return
482        else:
483            try:
484                file_open = open(filename, 'w')
485                text = ""
486                for key in self.recent_items:
487                    full_path = self.recent_items[key] + key
488                    new_line = full_path + '\n'
489                    text += new_line
490                file_open.write(text)
491                file_open.close()
492                   
493            except IOError:
494                print 'Unable to write the recent files to file: %s\n'\
495                    'No recent files will be written' % str(filename)
496
497    def findLastSlash(self, string):
498        """
499        Find the last slash in string string
500        @type string: string
501        @param string: The string to find the last slash in
502        @return: None
503        """
504        back_num = 1
505        start = len(string)
506        while (True):
507            new_num = start - back_num
508            if (string[new_num] == '/'):
509                last_slash = new_num
510                break
511            else:
512                back_num += 1
513        return last_slash
514
515    def resizeEvent(self, event):
516        """
517        Overrides the normal resize event so it will also resize the widgets within
518        @type event: QResizeEvent
519        @param event: the event (it's provided by the Qt system)
520        @return: None
521        """
522        self.ui.main_edit.setGeometry(QtCore.QRect(0, 0, event.size().width(),
523                                                   event.size().height()-73))
524        self.ui.dialog_map.setGeometry(QtCore.QRect(0, 0, event.size().width(),
525                                                    event.size().height()-73))
526
527    def closeEvent(self, event):
528        """
529        Overrides the normal close event so it will ask if you want to save changes etc
530        @type event: QCloseEvent
531        @param event: the event (its provided by the qt system)
532        @return: None
533        """
534        if (self.changes):
535            window = ChangesWindow()
536            ret = window.run()
537            if (ret == QtGui.QMessageBox.Save):
538                self.saveFile()
539                self.writeRecentItems("data/recent_files.txt")
540                event.accept()
541            elif (ret == QtGui.QMessageBox.Discard):
542                self.writeRecentItems("data/recent_files.txt")
543                event.accept()
544            elif (ret == QtGui.QMessageBox.Cancel):
545                event.ignore()
546               
547                       
548    def quit(self, filename):
549        """
550        Quit and then write the recent files to filename and ask about changes
551        @type filename: string
552        @param filename: the file to write to
553        @return: None
554        """
555        self.ui.writingEditor.close()
Note: See TracBrowser for help on using the repository browser.