|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the listspace viewmanager class. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt6.QtCore import pyqtSignal, pyqtSlot, QEvent, Qt |
|
13 from PyQt6.QtWidgets import ( |
|
14 QStackedWidget, QSplitter, QListWidget, QListWidgetItem, QSizePolicy, |
|
15 QMenu, QApplication |
|
16 ) |
|
17 |
|
18 from EricWidgets.EricApplication import ericApp |
|
19 |
|
20 from ViewManager.ViewManager import ViewManager |
|
21 |
|
22 import QScintilla.Editor |
|
23 from QScintilla.Editor import Editor |
|
24 from QScintilla.EditorAssembly import EditorAssembly |
|
25 |
|
26 import UI.PixmapCache |
|
27 import Preferences |
|
28 |
|
29 |
|
30 class StackedWidget(QStackedWidget): |
|
31 """ |
|
32 Class implementing a custimized StackedWidget. |
|
33 """ |
|
34 def __init__(self, parent): |
|
35 """ |
|
36 Constructor |
|
37 |
|
38 @param parent parent widget |
|
39 @type QWidget |
|
40 """ |
|
41 super().__init__(parent) |
|
42 |
|
43 self.editors = [] |
|
44 |
|
45 def addWidget(self, assembly): |
|
46 """ |
|
47 Public method to add a new widget. |
|
48 |
|
49 @param assembly editor assembly object to be added |
|
50 @type QScintilla.EditorAssembly.EditorAssembly |
|
51 """ |
|
52 editor = assembly.getEditor() |
|
53 super().addWidget(assembly) |
|
54 if editor not in self.editors: |
|
55 self.editors.append(editor) |
|
56 |
|
57 def removeWidget(self, widget): |
|
58 """ |
|
59 Public method to remove a widget. |
|
60 |
|
61 @param widget widget to be removed |
|
62 @type QWidget |
|
63 """ |
|
64 if isinstance(widget, QScintilla.Editor.Editor): |
|
65 self.editors.remove(widget) |
|
66 widget = widget.parent() |
|
67 super().removeWidget(widget) |
|
68 |
|
69 def currentWidget(self): |
|
70 """ |
|
71 Public method to get a reference to the current editor. |
|
72 |
|
73 @return reference to the current editor |
|
74 @rtype Editor |
|
75 """ |
|
76 widget = super().currentWidget() |
|
77 if widget is not None: |
|
78 widget = widget.getEditor() |
|
79 return widget |
|
80 |
|
81 def setCurrentWidget(self, widget): |
|
82 """ |
|
83 Public method to set the current widget. |
|
84 |
|
85 @param widget widget to be made current |
|
86 @type QWidget |
|
87 """ |
|
88 if widget is not None: |
|
89 if isinstance(widget, QScintilla.Editor.Editor): |
|
90 self.editors.remove(widget) |
|
91 self.editors.insert(0, widget) |
|
92 widget = widget.parent() |
|
93 super().setCurrentWidget(widget) |
|
94 |
|
95 def setCurrentIndex(self, index): |
|
96 """ |
|
97 Public method to set the current widget by its index. |
|
98 |
|
99 @param index index of widget to be made current |
|
100 @type int |
|
101 """ |
|
102 widget = self.widget(index) |
|
103 if widget is not None: |
|
104 self.setCurrentWidget(widget) |
|
105 |
|
106 def nextTab(self): |
|
107 """ |
|
108 Public slot used to show the next tab. |
|
109 """ |
|
110 ind = self.currentIndex() + 1 |
|
111 if ind == self.count(): |
|
112 ind = 0 |
|
113 |
|
114 self.setCurrentIndex(ind) |
|
115 self.currentWidget().setFocus() |
|
116 |
|
117 def prevTab(self): |
|
118 """ |
|
119 Public slot used to show the previous tab. |
|
120 """ |
|
121 ind = self.currentIndex() - 1 |
|
122 if ind == -1: |
|
123 ind = self.count() - 1 |
|
124 |
|
125 self.setCurrentIndex(ind) |
|
126 self.currentWidget().setFocus() |
|
127 |
|
128 def hasEditor(self, editor): |
|
129 """ |
|
130 Public method to check for an editor. |
|
131 |
|
132 @param editor editor object to check for |
|
133 @type Editor |
|
134 @return flag indicating, whether the editor to be checked belongs |
|
135 to the list of editors managed by this stacked widget. |
|
136 @rtype bool |
|
137 """ |
|
138 return editor in self.editors |
|
139 |
|
140 def firstEditor(self): |
|
141 """ |
|
142 Public method to retrieve the first editor in the list of managed |
|
143 editors. |
|
144 |
|
145 @return first editor in list |
|
146 @rtype QScintilla.Editor.Editor |
|
147 """ |
|
148 return len(self.editors) and self.editors[0] or None |
|
149 |
|
150 |
|
151 class Listspace(ViewManager): |
|
152 """ |
|
153 Class implementing the listspace viewmanager class. |
|
154 |
|
155 @signal changeCaption(str) emitted if a change of the caption is necessary |
|
156 @signal editorChanged(str) emitted when the current editor has changed |
|
157 @signal editorChangedEd(Editor) emitted when the current editor has changed |
|
158 @signal lastEditorClosed() emitted after the last editor window was closed |
|
159 @signal editorOpened(str) emitted after an editor window was opened |
|
160 @signal editorOpenedEd(Editor) emitted after an editor window was opened |
|
161 @signal editorClosed(str) emitted just before an editor window gets closed |
|
162 @signal editorClosedEd(Editor) emitted just before an editor window gets |
|
163 closed |
|
164 @signal editorRenamed(str) emitted after an editor was renamed |
|
165 @signal editorRenamedEd(Editor) emitted after an editor was renamed |
|
166 @signal editorSaved(str) emitted after an editor window was saved |
|
167 @signal editorSavedEd(Editor) emitted after an editor window was saved |
|
168 @signal checkActions(Editor) emitted when some actions should be checked |
|
169 for their status |
|
170 @signal cursorChanged(Editor) emitted after the cursor position of the |
|
171 active window has changed |
|
172 @signal breakpointToggled(Editor) emitted when a breakpoint is toggled. |
|
173 @signal bookmarkToggled(Editor) emitted when a bookmark is toggled. |
|
174 @signal syntaxerrorToggled(Editor) emitted when a syntax error is toggled. |
|
175 @signal previewStateChanged(bool) emitted to signal a change in the |
|
176 preview state |
|
177 @signal previewStateChanged(bool) emitted to signal a change in the |
|
178 preview state |
|
179 @signal astViewerStateChanged(bool) emitted to signal a change in the |
|
180 AST viewer state |
|
181 @signal editorLanguageChanged(Editor) emitted to signal a change of an |
|
182 editors language |
|
183 @signal editorTextChanged(Editor) emitted to signal a change of an |
|
184 editor's text |
|
185 @signal editorLineChanged(str,int) emitted to signal a change of an |
|
186 editor's current line (line is given one based) |
|
187 @signal editorLineChangedEd(Editor,int) emitted to signal a change of an |
|
188 editor's current line (line is given one based) |
|
189 """ |
|
190 changeCaption = pyqtSignal(str) |
|
191 editorChanged = pyqtSignal(str) |
|
192 editorChangedEd = pyqtSignal(Editor) |
|
193 lastEditorClosed = pyqtSignal() |
|
194 editorOpened = pyqtSignal(str) |
|
195 editorOpenedEd = pyqtSignal(Editor) |
|
196 editorClosed = pyqtSignal(str) |
|
197 editorClosedEd = pyqtSignal(Editor) |
|
198 editorRenamed = pyqtSignal(str) |
|
199 editorRenamedEd = pyqtSignal(Editor) |
|
200 editorSaved = pyqtSignal(str) |
|
201 editorSavedEd = pyqtSignal(Editor) |
|
202 checkActions = pyqtSignal(Editor) |
|
203 cursorChanged = pyqtSignal(Editor) |
|
204 breakpointToggled = pyqtSignal(Editor) |
|
205 bookmarkToggled = pyqtSignal(Editor) |
|
206 syntaxerrorToggled = pyqtSignal(Editor) |
|
207 previewStateChanged = pyqtSignal(bool) |
|
208 astViewerStateChanged = pyqtSignal(bool) |
|
209 editorLanguageChanged = pyqtSignal(Editor) |
|
210 editorTextChanged = pyqtSignal(Editor) |
|
211 editorLineChanged = pyqtSignal(str, int) |
|
212 editorLineChangedEd = pyqtSignal(Editor, int) |
|
213 |
|
214 def __init__(self, parent): |
|
215 """ |
|
216 Constructor |
|
217 |
|
218 @param parent parent widget |
|
219 @type QWidget |
|
220 """ |
|
221 self.stacks = [] |
|
222 |
|
223 self.__splitter = QSplitter(parent) |
|
224 ViewManager.__init__(self) |
|
225 self.__splitter.setChildrenCollapsible(False) |
|
226 |
|
227 self.viewlist = QListWidget(self) |
|
228 policy = self.viewlist.sizePolicy() |
|
229 policy.setHorizontalPolicy(QSizePolicy.Policy.Ignored) |
|
230 self.viewlist.setSizePolicy(policy) |
|
231 self.__splitter.addWidget(self.viewlist) |
|
232 self.viewlist.setContextMenuPolicy( |
|
233 Qt.ContextMenuPolicy.CustomContextMenu) |
|
234 self.viewlist.currentRowChanged.connect(self.__showSelectedView) |
|
235 self.viewlist.customContextMenuRequested.connect(self.__showMenu) |
|
236 |
|
237 self.stackArea = QSplitter(self) |
|
238 self.stackArea.setChildrenCollapsible(False) |
|
239 self.__splitter.addWidget(self.stackArea) |
|
240 self.stackArea.setOrientation(Qt.Orientation.Vertical) |
|
241 stack = StackedWidget(self.stackArea) |
|
242 self.stackArea.addWidget(stack) |
|
243 self.stacks.append(stack) |
|
244 self.currentStack = stack |
|
245 stack.currentChanged.connect(self.__currentChanged) |
|
246 stack.installEventFilter(self) |
|
247 self.__splitter.setSizes( |
|
248 [int(self.width() * 0.2), int(self.width() * 0.8)]) |
|
249 # 20% for viewlist, 80% for the editors |
|
250 self.__inRemoveView = False |
|
251 |
|
252 self.__initMenu() |
|
253 self.contextMenuEditor = None |
|
254 self.contextMenuIndex = -1 |
|
255 |
|
256 def __initMenu(self): |
|
257 """ |
|
258 Private method to initialize the viewlist context menu. |
|
259 """ |
|
260 self.__startMenu = QMenu(self.tr("Start"), self) |
|
261 self.__startMenu.addAction( |
|
262 UI.PixmapCache.getIcon("runScript"), |
|
263 self.tr('Run Script...'), |
|
264 self.__contextMenuRunScript) |
|
265 self.__startMenu.addAction( |
|
266 UI.PixmapCache.getIcon("debugScript"), |
|
267 self.tr('Debug Script...'), |
|
268 self.__contextMenuDebugScript) |
|
269 self.__startMenu.addAction( |
|
270 UI.PixmapCache.getIcon("profileScript"), |
|
271 self.tr('Profile Script...'), |
|
272 self.__contextMenuProfileScript) |
|
273 self.__startMenu.addAction( |
|
274 UI.PixmapCache.getIcon("coverageScript"), |
|
275 self.tr('Coverage run of Script...'), |
|
276 self.__contextMenuCoverageScript) |
|
277 |
|
278 self.__menu = QMenu(self) |
|
279 self.__menu.addAction( |
|
280 UI.PixmapCache.getIcon("tabClose"), |
|
281 self.tr('Close'), self.__contextMenuClose) |
|
282 self.closeOthersMenuAct = self.__menu.addAction( |
|
283 UI.PixmapCache.getIcon("tabCloseOther"), |
|
284 self.tr("Close Others"), |
|
285 self.__contextMenuCloseOthers) |
|
286 self.__menu.addAction( |
|
287 self.tr('Close All'), self.__contextMenuCloseAll) |
|
288 self.__menu.addSeparator() |
|
289 self.saveMenuAct = self.__menu.addAction( |
|
290 UI.PixmapCache.getIcon("fileSave"), |
|
291 self.tr('Save'), self.__contextMenuSave) |
|
292 self.__menu.addAction( |
|
293 UI.PixmapCache.getIcon("fileSaveAs"), |
|
294 self.tr('Save As...'), self.__contextMenuSaveAs) |
|
295 self.__menu.addAction( |
|
296 UI.PixmapCache.getIcon("fileSaveAll"), |
|
297 self.tr('Save All'), self.__contextMenuSaveAll) |
|
298 self.__menu.addSeparator() |
|
299 self.openRejectionsMenuAct = self.__menu.addAction( |
|
300 self.tr("Open 'rejection' file"), |
|
301 self.__contextMenuOpenRejections) |
|
302 self.__menu.addSeparator() |
|
303 self.__startAct = self.__menu.addMenu(self.__startMenu) |
|
304 self.__menu.addSeparator() |
|
305 self.__menu.addAction( |
|
306 UI.PixmapCache.getIcon("printPreview"), |
|
307 self.tr("Print Preview"), self.__contextMenuPrintPreviewFile) |
|
308 self.__menu.addAction( |
|
309 UI.PixmapCache.getIcon("print"), |
|
310 self.tr('Print'), self.__contextMenuPrintFile) |
|
311 self.__menu.addSeparator() |
|
312 self.copyPathAct = self.__menu.addAction( |
|
313 self.tr("Copy Path to Clipboard"), |
|
314 self.__contextMenuCopyPathToClipboard) |
|
315 |
|
316 def __showMenu(self, point): |
|
317 """ |
|
318 Private slot to handle the customContextMenuRequested signal of |
|
319 the viewlist. |
|
320 |
|
321 @param point position to open the menu at |
|
322 @type QPoint |
|
323 """ |
|
324 if self.editors: |
|
325 itm = self.viewlist.itemAt(point) |
|
326 if itm is not None: |
|
327 row = self.viewlist.row(itm) |
|
328 self.contextMenuEditor = self.editors[row] |
|
329 self.contextMenuIndex = row |
|
330 if self.contextMenuEditor: |
|
331 self.saveMenuAct.setEnabled( |
|
332 self.contextMenuEditor.isModified()) |
|
333 fileName = self.contextMenuEditor.getFileName() |
|
334 self.copyPathAct.setEnabled(bool(fileName)) |
|
335 if fileName: |
|
336 rej = "{0}.rej".format(fileName) |
|
337 self.openRejectionsMenuAct.setEnabled( |
|
338 os.path.exists(rej)) |
|
339 |
|
340 ext = os.path.splitext(fileName)[1] |
|
341 self.__startAct.setEnabled( |
|
342 ext in Preferences.getDebugger( |
|
343 "Python3Extensions").split() |
|
344 ) |
|
345 else: |
|
346 self.openRejectionsMenuAct.setEnabled(False) |
|
347 self.__startAct.setEnabled(False) |
|
348 |
|
349 self.closeOthersMenuAct.setEnabled( |
|
350 self.viewlist.count() > 1) |
|
351 |
|
352 self.__menu.popup(self.viewlist.mapToGlobal(point)) |
|
353 |
|
354 def mainWidget(self): |
|
355 """ |
|
356 Public method to return a reference to the main Widget of a |
|
357 specific view manager subclass. |
|
358 |
|
359 @return reference to the main widget |
|
360 @rtype QWidget |
|
361 """ |
|
362 return self.__splitter |
|
363 |
|
364 def canCascade(self): |
|
365 """ |
|
366 Public method to signal if cascading of managed windows is available. |
|
367 |
|
368 @return flag indicating cascading of windows is available |
|
369 @rtype bool |
|
370 """ |
|
371 return False |
|
372 |
|
373 def canTile(self): |
|
374 """ |
|
375 Public method to signal if tiling of managed windows is available. |
|
376 |
|
377 @return flag indicating tiling of windows is available |
|
378 @rtype bool |
|
379 """ |
|
380 return False |
|
381 |
|
382 def canSplit(self): |
|
383 """ |
|
384 public method to signal if splitting of the view is available. |
|
385 |
|
386 @return flag indicating splitting of the view is available |
|
387 @rtype bool |
|
388 """ |
|
389 return True |
|
390 |
|
391 def tile(self): |
|
392 """ |
|
393 Public method to tile the managed windows. |
|
394 """ |
|
395 pass |
|
396 |
|
397 def cascade(self): |
|
398 """ |
|
399 Public method to cascade the managed windows. |
|
400 """ |
|
401 pass |
|
402 |
|
403 def _removeAllViews(self): |
|
404 """ |
|
405 Protected method to remove all views (i.e. windows). |
|
406 """ |
|
407 self.viewlist.clear() |
|
408 for win in self.editors: |
|
409 for stack in self.stacks: |
|
410 if stack.hasEditor(win): |
|
411 stack.removeWidget(win) |
|
412 break |
|
413 win.closeIt() |
|
414 |
|
415 def _removeView(self, win): |
|
416 """ |
|
417 Protected method to remove a view (i.e. window). |
|
418 |
|
419 @param win editor window to be removed |
|
420 @type Editor |
|
421 """ |
|
422 self.__inRemoveView = True |
|
423 ind = self.editors.index(win) |
|
424 itm = self.viewlist.takeItem(ind) |
|
425 if itm: |
|
426 del itm |
|
427 for stack in self.stacks: |
|
428 if stack.hasEditor(win): |
|
429 stack.removeWidget(win) |
|
430 break |
|
431 win.closeIt() |
|
432 self.__inRemoveView = False |
|
433 if ind > 0: |
|
434 ind -= 1 |
|
435 else: |
|
436 if len(self.editors) > 1: |
|
437 ind = 1 |
|
438 else: |
|
439 return |
|
440 stack.setCurrentWidget(stack.firstEditor()) |
|
441 self._showView(self.editors[ind].parent()) |
|
442 |
|
443 aw = self.activeWindow() |
|
444 fn = aw and aw.getFileName() or None |
|
445 if fn: |
|
446 self.changeCaption.emit(fn) |
|
447 self.editorChanged.emit(fn) |
|
448 self.editorLineChanged.emit(fn, aw.getCursorPosition()[0] + 1) |
|
449 else: |
|
450 self.changeCaption.emit("") |
|
451 self.editorChangedEd.emit(aw) |
|
452 |
|
453 def _addView(self, win, fn=None, noName="", addNext=False, indexes=None): |
|
454 """ |
|
455 Protected method to add a view (i.e. window). |
|
456 |
|
457 @param win editor assembly to be added |
|
458 @type EditorAssembly |
|
459 @param fn filename of this editor |
|
460 @type str |
|
461 @param noName name to be used for an unnamed editor |
|
462 @type str |
|
463 @param addNext flag indicating to add the view next to the current |
|
464 view |
|
465 @type bool |
|
466 @param indexes of the editor, first the split view index, second the |
|
467 index within the view |
|
468 @type tuple of two int |
|
469 """ |
|
470 editor = win.getEditor() |
|
471 if not fn: |
|
472 if not noName: |
|
473 self.untitledCount += 1 |
|
474 noName = self.tr("Untitled {0}").format(self.untitledCount) |
|
475 self.viewlist.addItem(noName) |
|
476 editor.setNoName(noName) |
|
477 else: |
|
478 txt = os.path.basename(fn) |
|
479 if not os.access(fn, os.W_OK): |
|
480 txt = self.tr("{0} (ro)").format(txt) |
|
481 itm = QListWidgetItem(txt) |
|
482 itm.setToolTip(fn) |
|
483 self.viewlist.addItem(itm) |
|
484 if indexes: |
|
485 if indexes[0] < len(self.stacks): |
|
486 stack = self.stacks[indexes[0]] |
|
487 else: |
|
488 stack = self.stacks[-1] |
|
489 stack.addWidget(win) |
|
490 else: |
|
491 self.currentStack.addWidget(win) |
|
492 self.currentStack.setCurrentWidget(win) |
|
493 editor.captionChanged.connect(self.__captionChange) |
|
494 editor.cursorLineChanged.connect( |
|
495 lambda lineno: self.__cursorLineChanged(lineno, editor)) |
|
496 |
|
497 index = self.editors.index(editor) |
|
498 self.viewlist.setCurrentRow(index) |
|
499 editor.setFocus() |
|
500 if fn: |
|
501 self.changeCaption.emit(fn) |
|
502 self.editorChanged.emit(fn) |
|
503 self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1) |
|
504 else: |
|
505 self.changeCaption.emit("") |
|
506 self.editorChangedEd.emit(editor) |
|
507 |
|
508 def __captionChange(self, cap, editor): |
|
509 """ |
|
510 Private method to handle caption change signals from the editor. |
|
511 |
|
512 Updates the listwidget text to reflect the new caption information. |
|
513 |
|
514 @param cap Caption for the editor |
|
515 @type str |
|
516 @param editor Editor to update the caption for |
|
517 @type Editor |
|
518 """ |
|
519 fn = editor.getFileName() |
|
520 if fn: |
|
521 self.setEditorName(editor, fn) |
|
522 |
|
523 def __cursorLineChanged(self, lineno, editor): |
|
524 """ |
|
525 Private slot to handle a change of the current editor's cursor line. |
|
526 |
|
527 @param lineno line number of the editor's cursor (zero based) |
|
528 @type int |
|
529 @param editor reference to the editor |
|
530 @type Editor |
|
531 """ |
|
532 if editor: |
|
533 fn = editor.getFileName() |
|
534 if fn: |
|
535 self.editorLineChanged.emit(fn, lineno + 1) |
|
536 self.editorLineChangedEd.emit(editor, lineno + 1) |
|
537 |
|
538 def _showView(self, win, fn=None): |
|
539 """ |
|
540 Protected method to show a view (i.e. window). |
|
541 |
|
542 @param win editor assembly to be shown |
|
543 @type EditorAssembly |
|
544 @param fn filename of this editor |
|
545 @type string |
|
546 """ |
|
547 editor = win.getEditor() |
|
548 for stack in self.stacks: |
|
549 if stack.hasEditor(editor): |
|
550 stack.setCurrentWidget(win) |
|
551 self.currentStack = stack |
|
552 break |
|
553 index = self.editors.index(editor) |
|
554 self.viewlist.setCurrentRow(index) |
|
555 editor.setFocus() |
|
556 fn = editor.getFileName() |
|
557 if fn: |
|
558 self.changeCaption.emit(fn) |
|
559 self.editorChanged.emit(fn) |
|
560 self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1) |
|
561 else: |
|
562 self.changeCaption.emit("") |
|
563 self.editorChangedEd.emit(editor) |
|
564 |
|
565 def __showSelectedView(self, row): |
|
566 """ |
|
567 Private slot called to show a view selected in the list. |
|
568 |
|
569 @param row row number of the item clicked on |
|
570 @type int |
|
571 """ |
|
572 if row != -1: |
|
573 self._showView(self.editors[row].parent()) |
|
574 self._checkActions(self.editors[row]) |
|
575 |
|
576 def activeWindow(self): |
|
577 """ |
|
578 Public method to return the active (i.e. current) window. |
|
579 |
|
580 @return reference to the active editor |
|
581 @rtype EditorAssembly |
|
582 """ |
|
583 return self.currentStack.currentWidget() |
|
584 |
|
585 def showWindowMenu(self, windowMenu): |
|
586 """ |
|
587 Public method to set up the viewmanager part of the Window menu. |
|
588 |
|
589 @param windowMenu reference to the window menu |
|
590 @type QMenu |
|
591 """ |
|
592 pass |
|
593 |
|
594 def _initWindowActions(self): |
|
595 """ |
|
596 Protected method to define the user interface actions for window |
|
597 handling. |
|
598 """ |
|
599 pass |
|
600 |
|
601 def setEditorName(self, editor, newName): |
|
602 """ |
|
603 Public method to change the displayed name of the editor. |
|
604 |
|
605 @param editor editor window to be changed |
|
606 @type Editor |
|
607 @param newName new name to be shown |
|
608 @type str |
|
609 """ |
|
610 if newName: |
|
611 currentRow = self.viewlist.currentRow() |
|
612 index = self.editors.index(editor) |
|
613 txt = os.path.basename(newName) |
|
614 if not os.access(newName, os.W_OK): |
|
615 txt = self.tr("{0} (ro)").format(txt) |
|
616 itm = self.viewlist.item(index) |
|
617 if itm: |
|
618 itm.setText(txt) |
|
619 itm.setToolTip(newName) |
|
620 self.viewlist.setCurrentRow(currentRow) |
|
621 self.changeCaption.emit(newName) |
|
622 |
|
623 def _modificationStatusChanged(self, m, editor): |
|
624 """ |
|
625 Protected slot to handle the modificationStatusChanged signal. |
|
626 |
|
627 @param m flag indicating the modification status |
|
628 @type bool |
|
629 @param editor editor window changed |
|
630 @type Editor |
|
631 """ |
|
632 currentRow = self.viewlist.currentRow() |
|
633 index = self.editors.index(editor) |
|
634 keys = [] |
|
635 if m: |
|
636 keys.append("fileModified") |
|
637 if editor.hasSyntaxErrors(): |
|
638 keys.append("syntaxError22") |
|
639 elif editor.hasWarnings(): |
|
640 keys.append("warning22") |
|
641 if not keys: |
|
642 keys.append("empty") |
|
643 item = self.viewlist.item(index) |
|
644 if item: |
|
645 item.setIcon(UI.PixmapCache.getCombinedIcon(keys)) |
|
646 self.viewlist.setCurrentRow(currentRow) |
|
647 self._checkActions(editor) |
|
648 |
|
649 def _syntaxErrorToggled(self, editor): |
|
650 """ |
|
651 Protected slot to handle the syntaxerrorToggled signal. |
|
652 |
|
653 @param editor editor that sent the signal |
|
654 @type Editor |
|
655 """ |
|
656 currentRow = self.viewlist.currentRow() |
|
657 index = self.editors.index(editor) |
|
658 keys = [] |
|
659 if editor.isModified(): |
|
660 keys.append("fileModified") |
|
661 if editor.hasSyntaxErrors(): |
|
662 keys.append("syntaxError22") |
|
663 elif editor.hasWarnings(): |
|
664 keys.append("warning22") |
|
665 if not keys: |
|
666 keys.append("empty") |
|
667 item = self.viewlist.item(index) |
|
668 if item: |
|
669 item.setIcon(UI.PixmapCache.getCombinedIcon(keys)) |
|
670 self.viewlist.setCurrentRow(currentRow) |
|
671 |
|
672 ViewManager._syntaxErrorToggled(self, editor) |
|
673 |
|
674 def addSplit(self): |
|
675 """ |
|
676 Public method used to split the current view. |
|
677 """ |
|
678 stack = StackedWidget(self.stackArea) |
|
679 stack.show() |
|
680 self.stackArea.addWidget(stack) |
|
681 self.stacks.append(stack) |
|
682 self.currentStack = stack |
|
683 stack.currentChanged.connect(self.__currentChanged) |
|
684 stack.installEventFilter(self) |
|
685 size = ( |
|
686 self.stackArea.width() |
|
687 if self.stackArea.orientation() == Qt.Orientation.Horizontal else |
|
688 self.stackArea.height() |
|
689 ) |
|
690 self.stackArea.setSizes( |
|
691 [int(size / len(self.stacks))] * len(self.stacks)) |
|
692 self.splitRemoveAct.setEnabled(True) |
|
693 self.nextSplitAct.setEnabled(True) |
|
694 self.prevSplitAct.setEnabled(True) |
|
695 |
|
696 @pyqtSlot() |
|
697 def removeSplit(self, index=-1): |
|
698 """ |
|
699 Public method used to remove the current split view or a split view |
|
700 by index. |
|
701 |
|
702 @param index index of the split to be removed (-1 means to |
|
703 delete the current split) |
|
704 @type int |
|
705 @return flag indicating successful deletion |
|
706 @rtype bool |
|
707 """ |
|
708 if len(self.stacks) > 1: |
|
709 if index == -1: |
|
710 stack = self.currentStack |
|
711 else: |
|
712 if index < len(self.stacks): |
|
713 stack = self.stacks[index] |
|
714 else: |
|
715 stack = self.stacks[-1] |
|
716 res = True |
|
717 savedEditors = stack.editors[:] |
|
718 for editor in savedEditors: |
|
719 res &= self.closeEditor(editor) |
|
720 if res: |
|
721 try: |
|
722 i = self.stacks.index(stack) |
|
723 except ValueError: |
|
724 return True |
|
725 if i == len(self.stacks) - 1: |
|
726 i -= 1 |
|
727 self.stacks.remove(stack) |
|
728 stack.close() |
|
729 self.currentStack = self.stacks[i] |
|
730 if len(self.stacks) == 1: |
|
731 self.splitRemoveAct.setEnabled(False) |
|
732 self.nextSplitAct.setEnabled(False) |
|
733 self.prevSplitAct.setEnabled(False) |
|
734 return True |
|
735 |
|
736 return False |
|
737 |
|
738 def splitCount(self): |
|
739 """ |
|
740 Public method to get the number of splitted views. |
|
741 |
|
742 @return number of splitted views |
|
743 @rtype int |
|
744 """ |
|
745 return len(self.stacks) |
|
746 |
|
747 def setSplitCount(self, count): |
|
748 """ |
|
749 Public method to set the number of split views. |
|
750 |
|
751 @param count number of split views |
|
752 @type int |
|
753 """ |
|
754 if count > self.splitCount(): |
|
755 while self.splitCount() < count: |
|
756 self.addSplit() |
|
757 elif count < self.splitCount(): |
|
758 while self.splitCount() > count: |
|
759 # use an arbitrarily large index to remove the last one |
|
760 self.removeSplit(index=100) |
|
761 |
|
762 def getSplitOrientation(self): |
|
763 """ |
|
764 Public method to get the orientation of the split view. |
|
765 |
|
766 @return orientation of the split |
|
767 @rtype Qt.Orientation.Horizontal or Qt.Orientation.Vertical |
|
768 """ |
|
769 return self.stackArea.orientation() |
|
770 |
|
771 def setSplitOrientation(self, orientation): |
|
772 """ |
|
773 Public method used to set the orientation of the split view. |
|
774 |
|
775 @param orientation orientation of the split |
|
776 @type Qt.Orientation.Horizontal or Qt.Orientation.Vertical |
|
777 """ |
|
778 self.stackArea.setOrientation(orientation) |
|
779 |
|
780 def nextSplit(self): |
|
781 """ |
|
782 Public slot used to move to the next split. |
|
783 """ |
|
784 aw = self.activeWindow() |
|
785 _hasFocus = aw and aw.hasFocus() |
|
786 ind = self.stacks.index(self.currentStack) + 1 |
|
787 if ind == len(self.stacks): |
|
788 ind = 0 |
|
789 |
|
790 self.currentStack = self.stacks[ind] |
|
791 if _hasFocus: |
|
792 aw = self.activeWindow() |
|
793 if aw: |
|
794 aw.setFocus() |
|
795 |
|
796 cw = self.currentStack.currentWidget() |
|
797 if cw: |
|
798 index = self.editors.index(cw) |
|
799 self.viewlist.setCurrentRow(index) |
|
800 |
|
801 def prevSplit(self): |
|
802 """ |
|
803 Public slot used to move to the previous split. |
|
804 """ |
|
805 aw = self.activeWindow() |
|
806 _hasFocus = aw and aw.hasFocus() |
|
807 ind = self.stacks.index(self.currentStack) - 1 |
|
808 if ind == -1: |
|
809 ind = len(self.stacks) - 1 |
|
810 |
|
811 self.currentStack = self.stacks[ind] |
|
812 if _hasFocus: |
|
813 aw = self.activeWindow() |
|
814 if aw: |
|
815 aw.setFocus() |
|
816 |
|
817 cw = self.currentStack.currentWidget() |
|
818 if cw: |
|
819 index = self.editors.index(cw) |
|
820 self.viewlist.setCurrentRow(index) |
|
821 |
|
822 def __contextMenuClose(self): |
|
823 """ |
|
824 Private method to close the selected editor. |
|
825 """ |
|
826 if self.contextMenuEditor: |
|
827 self.closeEditorWindow(self.contextMenuEditor) |
|
828 |
|
829 def __contextMenuCloseOthers(self): |
|
830 """ |
|
831 Private method to close the other editors. |
|
832 """ |
|
833 index = self.contextMenuIndex |
|
834 for i in ( |
|
835 list(range(self.viewlist.count() - 1, index, -1)) + |
|
836 list(range(index - 1, -1, -1)) |
|
837 ): |
|
838 editor = self.editors[i] |
|
839 self.closeEditorWindow(editor) |
|
840 |
|
841 def __contextMenuCloseAll(self): |
|
842 """ |
|
843 Private method to close all editors. |
|
844 """ |
|
845 savedEditors = self.editors[:] |
|
846 for editor in savedEditors: |
|
847 self.closeEditorWindow(editor) |
|
848 |
|
849 def __contextMenuSave(self): |
|
850 """ |
|
851 Private method to save the selected editor. |
|
852 """ |
|
853 if self.contextMenuEditor: |
|
854 self.saveEditorEd(self.contextMenuEditor) |
|
855 |
|
856 def __contextMenuSaveAs(self): |
|
857 """ |
|
858 Private method to save the selected editor to a new file. |
|
859 """ |
|
860 if self.contextMenuEditor: |
|
861 self.saveAsEditorEd(self.contextMenuEditor) |
|
862 |
|
863 def __contextMenuSaveAll(self): |
|
864 """ |
|
865 Private method to save all editors. |
|
866 """ |
|
867 self.saveEditorsList(self.editors) |
|
868 |
|
869 def __contextMenuOpenRejections(self): |
|
870 """ |
|
871 Private slot to open a rejections file associated with the selected |
|
872 editor. |
|
873 """ |
|
874 if self.contextMenuEditor: |
|
875 fileName = self.contextMenuEditor.getFileName() |
|
876 if fileName: |
|
877 rej = "{0}.rej".format(fileName) |
|
878 if os.path.exists(rej): |
|
879 self.openSourceFile(rej) |
|
880 |
|
881 def __contextMenuPrintFile(self): |
|
882 """ |
|
883 Private method to print the selected editor. |
|
884 """ |
|
885 if self.contextMenuEditor: |
|
886 self.printEditor(self.contextMenuEditor) |
|
887 |
|
888 def __contextMenuPrintPreviewFile(self): |
|
889 """ |
|
890 Private method to show a print preview of the selected editor. |
|
891 """ |
|
892 if self.contextMenuEditor: |
|
893 self.printPreviewEditor(self.contextMenuEditor) |
|
894 |
|
895 def __contextMenuCopyPathToClipboard(self): |
|
896 """ |
|
897 Private method to copy the file name of the selected editor to the |
|
898 clipboard. |
|
899 """ |
|
900 if self.contextMenuEditor: |
|
901 fn = self.contextMenuEditor.getFileName() |
|
902 if fn: |
|
903 cb = QApplication.clipboard() |
|
904 cb.setText(fn) |
|
905 |
|
906 def __contextMenuRunScript(self): |
|
907 """ |
|
908 Private method to run the editor script. |
|
909 """ |
|
910 if self.contextMenuEditor: |
|
911 fn = self.contextMenuEditor.getFileName() |
|
912 if fn: |
|
913 ericApp().getObject("DebugUI").doRun(False, script=fn) |
|
914 |
|
915 def __contextMenuDebugScript(self): |
|
916 """ |
|
917 Private method to debug the editor script. |
|
918 """ |
|
919 if self.contextMenuEditor: |
|
920 fn = self.contextMenuEditor.getFileName() |
|
921 if fn: |
|
922 ericApp().getObject("DebugUI").doDebug(False, script=fn) |
|
923 |
|
924 def __contextMenuProfileScript(self): |
|
925 """ |
|
926 Private method to profile the editor script. |
|
927 """ |
|
928 if self.contextMenuEditor: |
|
929 fn = self.contextMenuEditor.getFileName() |
|
930 if fn: |
|
931 ericApp().getObject("DebugUI").doProfile(False, script=fn) |
|
932 |
|
933 def __contextMenuCoverageScript(self): |
|
934 """ |
|
935 Private method to run a coverage test of the editor script. |
|
936 """ |
|
937 if self.contextMenuEditor: |
|
938 fn = self.contextMenuEditor.getFileName() |
|
939 if fn: |
|
940 ericApp().getObject("DebugUI").doCoverage(False, script=fn) |
|
941 |
|
942 def __currentChanged(self, index): |
|
943 """ |
|
944 Private slot to handle the currentChanged signal. |
|
945 |
|
946 @param index index of the current editor |
|
947 @type int |
|
948 """ |
|
949 if index == -1 or not self.editors: |
|
950 return |
|
951 |
|
952 editor = self.activeWindow() |
|
953 if editor is None: |
|
954 return |
|
955 |
|
956 self._checkActions(editor) |
|
957 editor.setFocus() |
|
958 fn = editor.getFileName() |
|
959 if fn: |
|
960 self.changeCaption.emit(fn) |
|
961 if not self.__inRemoveView: |
|
962 self.editorChanged.emit(fn) |
|
963 self.editorLineChanged.emit( |
|
964 fn, editor.getCursorPosition()[0] + 1) |
|
965 else: |
|
966 self.changeCaption.emit("") |
|
967 self.editorChangedEd.emit(editor) |
|
968 |
|
969 cindex = self.editors.index(editor) |
|
970 self.viewlist.setCurrentRow(cindex) |
|
971 |
|
972 def eventFilter(self, watched, event): |
|
973 """ |
|
974 Public method called to filter the event queue. |
|
975 |
|
976 @param watched the QObject being watched |
|
977 @type QObject |
|
978 @param event the event that occurred |
|
979 @type QEvent |
|
980 @return flag indicating, if we handled the event |
|
981 @rtype bool |
|
982 """ |
|
983 if ( |
|
984 event.type() == QEvent.Type.MouseButtonPress and |
|
985 event.button() != Qt.MouseButton.RightButton |
|
986 ): |
|
987 switched = True |
|
988 if isinstance(watched, QStackedWidget): |
|
989 switched = watched is not self.currentStack |
|
990 self.currentStack = watched |
|
991 elif isinstance(watched, QScintilla.Editor.Editor): |
|
992 for stack in self.stacks: |
|
993 if stack.hasEditor(watched): |
|
994 switched = stack is not self.currentStack |
|
995 self.currentStack = stack |
|
996 break |
|
997 currentWidget = self.currentStack.currentWidget() |
|
998 if currentWidget: |
|
999 index = self.editors.index(currentWidget) |
|
1000 self.viewlist.setCurrentRow(index) |
|
1001 |
|
1002 aw = self.activeWindow() |
|
1003 if aw is not None: |
|
1004 self._checkActions(aw) |
|
1005 aw.setFocus() |
|
1006 fn = aw.getFileName() |
|
1007 if fn: |
|
1008 self.changeCaption.emit(fn) |
|
1009 if switched: |
|
1010 self.editorChanged.emit(fn) |
|
1011 self.editorLineChanged.emit( |
|
1012 fn, aw.getCursorPosition()[0] + 1) |
|
1013 else: |
|
1014 self.changeCaption.emit("") |
|
1015 self.editorChangedEd.emit(aw) |
|
1016 |
|
1017 return False |
|
1018 |
|
1019 def getOpenEditorsForSession(self): |
|
1020 """ |
|
1021 Public method to get a lists of all open editors. |
|
1022 |
|
1023 The returned list contains one list per split view. If the view manager |
|
1024 cannot split the view, only one list of editors is returned. |
|
1025 |
|
1026 @return list of list of editor references |
|
1027 @rtype list of list of Editor |
|
1028 """ |
|
1029 editorLists = [] |
|
1030 for stack in self.stacks: |
|
1031 editors = [] |
|
1032 for index in range(stack.count()): |
|
1033 widget = stack.widget(index) |
|
1034 if isinstance(widget, EditorAssembly): |
|
1035 editor = widget.getEditor() |
|
1036 editors.append(editor) |
|
1037 editorLists.append(editors) |
|
1038 return editorLists |