def build_menu(self): menubar = self.menuBar() menubar.setNativeMenuBar(False) bar = self.menuBar() # File Menu fileMenu = bar.addMenu('&File') savePresetAction = QAction('Save Current State', self) savePresetAction.triggered.connect(self.open_save_dialog) fileMenu.addAction(savePresetAction) openSimAction = QAction('Open Simulation', self) openSimAction.triggered.connect(self.open_sim_dialog) fileMenu.addAction(openSimAction) makeAMovieAction = QAction('Make a Movie', self) makeAMovieAction.triggered.connect(self.open_movie_dialog) fileMenu.addAction(makeAMovieAction) exitAct = QAction(' &Quit', self) exitAct.setShortcut('Ctrl+Q') exitAct.triggered.connect(qApp.quit) fileMenu.addAction(exitAct) self.views_menu = bar.addMenu("Preset Views") self.views_update() self.view_dir_watcher = QFileSystemWatcher() self.view_dir_watcher.addPath( os.path.join(self.IseultDir, '.iseult_configs')) self.view_dir_watcher.directoryChanged.connect(self.views_update)
def on_pb_SearchDir( self ): ''' Called when the user presses the Browse button ''' logging.debug( "Browse button pressed" ) options = QtWidgets.QFileDialog.Options() options |= QtWidgets.QFileDialog.DontUseNativeDialog directoryName = QtWidgets.QFileDialog.getExistingDirectory( None, "Select Output Folder", "", options=options) if directoryName: logging.debug( "setting directory name: " + directoryName ) self.model.setDirectoryName( directoryName ) self.refreshDir() if self.changeDetection == True: self._pathToWatch = directoryName self._fileSysWatcher = QFileSystemWatcher() self._initialContent = os.listdir(self._pathToWatch) for f in self._initialContent: if (f != "trace.log"): self._fileSysWatcher.addPath(f) self._fileSysWatcher.fileChanged.connect(self.slotDirChanged) self._fileSysWatcher.directoryChanged.connect(self.slotDirChanged)
def __init__(self): """The constructor, which calls the super-class-contructor (Extension). Loads and sets all settings from settings file. Adds .blend as a supported file extension. Adds (stl, obj, x3d, ply) as supported file extensions for conversion. Adds menu items and the filewatcher trigger function. """ global fs_watcher super().__init__() # Loads and sets all settings from settings file. self.loadAndSetSettings() self._supported_extensions = ['.blend'] self._supported_foreign_extensions = ['stl', 'obj', 'x3d', 'ply'] # Adds filewatcher and it's connection for blender files. fs_watcher = QFileSystemWatcher() fs_watcher.fileChanged.connect(self._fileChanged) # Adds filewatcher and it's connection for foreign files. self._foreign_file_watcher = QFileSystemWatcher() self._foreign_file_watcher.fileChanged.connect( self._foreignFileChanged) # Builds the extension menu. self.setMenuName(catalog.i18nc('@item:inmenu', 'CuraBlender')) self.addMenuItem(catalog.i18nc('@item:inmenu', 'Open in Blender'), self._setUpFilePathForBlender) self.addMenuItem(catalog.i18nc('@item:inmenu', 'Settings'), self._openSettingsWindow) self.addMenuItem(catalog.i18nc('@item:inmenu', 'Debug Blenderpath'), self._showBlenderPath)
def __init__(self, gtomain, parent=None): try: super(gtoRemote, self).__init__(parent) self.gtomain = gtomain self.iface = gtomain.iface self.debug = gtomain.debug self.info = gtoInfo(self) self.fs_watcher = None if 'remote_watch_file' in gtomain.settings: remote_watch_path = gtomain.settings.get( 'remote_watch_file', None) if remote_watch_path is not None and remote_watch_path != "": remote_watch_path = self.gtomain.helper.getFilePath( remote_watch_path) self.remote_watch_file = os.path.basename( remote_watch_path) self.remote_watch_dir = os.path.dirname(remote_watch_path) if self.debug: self.info.log("Watching:", self.remote_watch_dir) if not os.path.exists(self.remote_watch_dir): os.makedirs(self.remote_watch_dir) if self.debug: self.info.log("Created:", self.remote_watch_dir) self.paths = [self.remote_watch_dir] self.fs_watcher = QFileSystemWatcher(self.paths) #if file already exists self.directory_changed(self.paths[0]) self.fs_watcher.directoryChanged.connect( self.directory_changed) self.fs_watcher.fileChanged.connect(self.file_changed) except Exception as e: self.info.err(e)
class EditorPidWatcher(QObject): appeared = pyqtSignal() def __init__(self, directory, parent=None): super().__init__(parent) self._pidfile = directory / 'editor_pid' self._watcher = QFileSystemWatcher(self) self._watcher.addPath(str(directory)) self._watcher.directoryChanged.connect(self._check_update) self.has_pidfile = False self._check_update() @pyqtSlot() def _check_update(self): if self.has_pidfile: return if self._pidfile.check(): if self._pidfile.read(): self.has_pidfile = True self.appeared.emit() else: self._watcher.addPath(str(self._pidfile)) def manual_check(self): return self._pidfile.check()
def __init__(self,ebeveyn=None): super(Gonderici,self).__init__(ebeveyn) kutu = QVBoxLayout() self.ebeveyn = ebeveyn self.setLayout(kutu) kutu.setContentsMargins(5,5,5,5) self.mesaj_liste = QListWidget() self.mesaj_liste.setSelectionMode(QListView.ExtendedSelection) kutu.addWidget(self.mesaj_liste) self.tum_mesajlar_fonk() self.dosya_izleyici = QFileSystemWatcher() self.dosya_izleyici.addPath(self.MESAJ_DIZINI) self.dosya_izleyici.directoryChanged.connect(self.tum_mesajlar_fonk) kutu_h = QHBoxLayout() kutu_h.addWidget(QLabel("<b>Mesaj Tipi :</b>")) self.mesaj_tipi_text = QComboBox() self.mesaj_tipi_text.addItems(["------","bilgi","sistem","kritik",]) kutu_h.addWidget(self.mesaj_tipi_text) kutu.addLayout(kutu_h) kutu_h = QHBoxLayout() # kutu_h.addWidget(QLabel("<b>Mesaj :</b>")) self.gonderilen_text = QTextEdit() self.gonderilen_text.setFixedHeight(100) kutu_h.addWidget(self.gonderilen_text) self.gonder_dugme = QPushButton("Gönder") self.gonder_dugme.setFixedHeight(100) self.gonder_dugme.clicked.connect(self.gonder_fonk) kutu_h.addWidget(self.gonder_dugme) kutu.addLayout(kutu_h)
def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.centralwidget.hide() self.project = ProjectManager() self.modules_manager = ModuleManager(self) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) self.modules_manager.init_modules([ CompilerModule, LevelWidget, AssetViewWidget, AssetBrowser, LogWidget, ProfilerWidget, ScriptEditorManager ]) self.file_watch = QFileSystemWatcher(self) self.file_watch.fileChanged.connect(self.file_changed) self.file_watch.directoryChanged.connect(self.dir_changed) self.build_file_watch = QFileSystemWatcher(self) self.build_file_watch.fileChanged.connect(self.build_file_changed) self.build_file_watch.directoryChanged.connect(self.build_dir_changed)
def test_killed_command(qtbot, tmpdir, py_proc, runner): text_file = tmpdir / 'text' text_file.write('This is text') pidfile = tmpdir / 'pid' watcher = QFileSystemWatcher() watcher.addPath(str(tmpdir)) env = {'QUTE_TEXT': str(text_file)} cmd, args = py_proc(r""" import os import time import sys # We can't use QUTE_FIFO to transmit the PID because that wouldn't work # on Windows, where QUTE_FIFO is only monitored after the script has # exited. with open(sys.argv[1], 'w') as f: f.write(str(os.getpid())) time.sleep(30) """) args.append(str(pidfile)) with qtbot.waitSignal(watcher.directoryChanged, timeout=10000): runner.run(cmd, *args, env=env) # Make sure the PID was written to the file, not just the file created time.sleep(0.5) with qtbot.waitSignal(runner.finished): os.kill(int(pidfile.read()), signal.SIGTERM) assert not text_file.exists()
def __init__(self, parent=None): super().__init__(parent) self.mWatchCount = QMap() self.mWatcher = QFileSystemWatcher(self) self.mWatcher.fileChanged.connect(self.onFileChanged) self.mWatcher.directoryChanged.connect(self.onDirectoryChanged)
def __init__(self): """Loads the UI-file and sets up the GUI.""" super(AutocutView, self).__init__() uic.loadUi(Resources.files.autocut_view, self) self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint) self.setFixedSize(self.size()) self.init_stylesheet() "QSS HOT RELOAD" self.__qss_watcher = QFileSystemWatcher() self.__qss_watcher.addPath(Resources.files.qss_dark) self.__qss_watcher.fileChanged.connect(self.update_qss) # centering the window rectangle = self.frameGeometry() center_point = QDesktopWidget().availableGeometry().center() rectangle.moveCenter(center_point) self.move(rectangle.topLeft()) cross_path = Resources.images.cross tick_path = Resources.images.tick self.cross = QPixmap(cross_path).scaledToHeight(50, mode=1) self.tick = QPixmap(tick_path).scaledToHeight(50, mode=1) self.video_image_label.setPixmap(self.cross) self.pdf_image_label.setPixmap(self.cross)
def __init__(self): super().__init__() self._watcher = QFileSystemWatcher(self) self._watcher.directoryChanged.connect(self.onChanged) self._watcher.fileChanged.connect(self.onChanged) self._events = {}
def run_masscan(self): self.process = QProcess(self) response = IpAsnRangeDoc().search().query('match', owner='amazon.com').execute() tmp_file = '/tmp/masscan_in.tmp' with open(tmp_file, 'a') as f: for iprange_doc in response: for range in iprange_doc.ranges: debug('scan cidr %s' % range.cidr) f.write('%s\n' % range.cidr) params = ['--output-format', 'json', '--output-filename', '/tmp/masscan_out.tmp', '--source-port', '60000', '-p%s' % ''.join(str(self.scan_ports)[1:-1]),'--rate', '%s' % self.spinBox.value(), '-iL', '/tmp/masscan_in.tmp'] debug('masscan params' % params) self.process.start('masscan', params) self.startScanButton.setEnabled(False) self.startScanButton.setText('starting..') self.process.waitForStarted(1) self.startScanButton.setText('stop scan') self.startScanButton.setEnabled(True) self.spinBox.setEnabled(False) watch = QFileSystemWatcher(self) watch.addPath('/tmp/masscan_out.tmp') watch.fileChanged.connect(functools.partial(self.data_available)) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.readyReadStandardError.connect(functools.partial(self.stderrReady)) self.process.stateChanged.connect(functools.partial(self.process_state_change))
def __init__(self, path, notebook): """ Class representing the actual tab pane for a plain .txt note file, including the editor, the toolbar and the save/load logic. path is the path to the file(which does not need to exist yet) notebook must be the Notebook instance the note will be a part of """ QWidget.__init__(self) self.notebook = notebook self.path = path #Set up the embedded webkit self.edit = QTextEdit() #set up the toolbar self.tools = QWidget() self.tools.lo = QHBoxLayout() self.tools.setLayout(self.tools.lo) if self.path: #Watch the file so we can auto reload self.watcher = QFileSystemWatcher() self.watcher.addPath(self.path) self.watcher.fileChanged.connect(self.reload) #Put the widgets together self.lo = QVBoxLayout() self.setLayout(self.lo) self.tools = NoteToolBar(self, richtext=False) self.lo.addWidget(self.tools) self.lo.addWidget(self.edit) self.reload()
def __init__(self, parent=None, watch=False): super().__init__(parent) self._filename = None self._proc = None self._remove_file = None self._watcher = QFileSystemWatcher(parent=self) if watch else None self._content = None
class AppBuffer(BrowserBuffer): def __init__(self, buffer_id, url, config_dir, arguments, emacs_var_dict, module_path): BrowserBuffer.__init__(self, buffer_id, url, config_dir, arguments, emacs_var_dict, module_path, False) self.url = url self.preview_file = tempfile.mkstemp(prefix='eaf-', suffix='.html', text=True)[1] self.render_js = os.path.join(os.path.dirname(__file__), "render.js") self.server_port = get_free_port() self.dark_mode = "false" if emacs_var_dict["eaf-markdown-dark-mode"] == "true" or \ (emacs_var_dict["eaf-markdown-dark-mode"] == "follow" and emacs_var_dict["eaf-emacs-theme-mode"] == "dark"): self.dark_mode = "true" self.draw_progressbar = True self.run_render_server() self.render() self.file_watcher = QFileSystemWatcher() self.file_watcher.fileChanged.connect(self.on_file_changed) self.file_watcher.addPath(url) def run_render_server(self): args = ["node", self.render_js, str(self.server_port)] subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) def on_file_changed(self, *args): self.render() def retry_if_connection_refused(ex): return isinstance(ex, URLError) and isinstance(ex.reason, ConnectionRefusedError) @retry(wait_fixed=500, stop_max_attempt_number=10, retry_on_exception=retry_if_connection_refused) def render(self): params = { "input_file": self.url, "output_file": self.preview_file, "dark_mode": self.dark_mode } url = 'http://localhost:{}?{}'.format(self.server_port, urlencode(params)) with urlopen(url) as f: resp = f.read().decode("utf-8") if resp == "ok": self.buffer_widget.load(QUrl.fromLocalFile(self.preview_file)) if platform.system() == "Windows": eval_in_emacs('eaf-activate-emacs-window', []) else: message_to_emacs("preview failed: {}".format(resp))
def __init__(self, parent=None): super(Editor, self).__init__(parent) ComponentMixin.__init__(self) self._filename = '' self.setup_editor(linenumbers=True, markers=True, edge_line=False, tab_mode=False, show_blanks=True, language='Python') self._actions = \ {'File' : [QAction(icon('new'), 'New', self, shortcut='ctrl+N', triggered=self.new), QAction(icon('open'), 'Open', self, shortcut='ctrl+O', triggered=self.open), QAction(icon('save'), 'Save', self, shortcut='ctrl+S', triggered=self.save), QAction(icon('save_as'), 'Save as', self, shortcut='ctrl+shift+S', triggered=self.save_as), QAction(icon('autoreload'), 'Automatic reload and preview', self,triggered=self.autoreload, checkable=True, checked=False, objectName='autoreload'), ]} for a in self._actions.values(): self.addActions(a) self._fixContextMenu() self.updatePreferences() # autoreload support self._file_watcher = QFileSystemWatcher(self) self._watched_file = None # we wait for 50ms after a file change for the file to be written completely self._file_watch_timer = QTimer(self) self._file_watch_timer.setInterval(50) self._file_watch_timer.setSingleShot(True) self._file_watcher.fileChanged.connect( lambda val: self._file_watch_timer.start()) self._file_watch_timer.timeout.connect(self._file_changed)
def __init__(self, directory, parent=None): super().__init__(parent) self._pidfile = directory / 'editor_pid' self._watcher = QFileSystemWatcher(self) self._watcher.addPath(str(directory)) self._watcher.directoryChanged.connect(self._check_update) self.has_pidfile = False self._check_update()
def __init__(self, csspath): self.path = csspath #Watch the file so we can auto reload self.csswatcher = QFileSystemWatcher() self.csswatcher.addPath(csspath) self.csswatcher.fileChanged.connect(self.rlcss) print("Watching " + self.path)
def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui = Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.ui.toolBox.setCurrentIndex(0) self.fileWatcher = QFileSystemWatcher() self.fileWatcher.directoryChanged.connect(self.do_directoryChanged) self.fileWatcher.fileChanged.connect(self.do_fileChanged)
def start_watching(self): """Create a file system watcher and connect its fileChanged SIGNAL to our _file_changed SLOT""" if not self.__watcher: self.__watcher = QFileSystemWatcher(self) self.__watcher.fileChanged['const QString&'].connect(self._file_changed) if self._file_path is not None: self.__mtime = os.path.getmtime(self._file_path) self.__watcher.addPath(self._file_path)
def setup_editor(self): self.rom_stream = tempfile.SpooledTemporaryFile( max_size=self.TEMP_MAX_RAM_USE, mode="w+") self.rom_path = None self.lst_parser = None self.rom_watcher = QFileSystemWatcher() self.rom_watcher.fileChanged.connect(self.reload_rom) self.spinBox.setValue(500) self.on_new()
def __init__(self, fname=None, data=None, comment='#', skiprows=0, delimiter=' ', expressions={}, autoupdate=False, parent=None, matplotlib=False, plotIndex=None, colors=None): QDialog.__init__(self, parent=parent) loadUi('UI_Forms/Data_Dialog.ui', self) self.colcycler = cycle(['r', 'g', 'b', 'c', 'm', 'y', 'w']) self.plotWidget = PlotWidget(parent=self, matplotlib=matplotlib) self.plotTab = self.tabWidget.addTab(self.plotWidget, 'Plots') self.tabWidget.setCurrentIndex(0) self.show() self.fileWatcher = QFileSystemWatcher() self.fileWatcher.fileChanged.connect(self.fileUpdated) self.cwd = None self.plotNum = 0 self.xlabel = [] self.ylabel = [] self.oldPlotIndex = {} self.oldColors = {} self.dataAltered = False self.expressions = expressions if data is not None: self.data = data self.autoUpdateCheckBox.setEnabled(False) elif fname is not None: self.data = self.readData(fname, comment=comment, skiprows=skiprows, delimiter=delimiter) else: self.data = None self.autoUpdateCheckBox.setEnabled(False) self.saveDataPushButton.setEnabled(False) self.addRowPushButton.setEnabled(False) self.removeRowsPushButton.setEnabled(False) self.removeColumnPushButton.setEnabled(False) if self.data is not None: self.setMeta2Table() self.setData2Table() if plotIndex is None: self.addPlots(color=None) else: self.addMultiPlots(plotIndex=plotIndex, colors=colors) self.init_signals() self.okPushButton.setAutoDefault(False) self.make_default() self.setWindowTitle('Data Dialog') self.acceptData = True
class MainWatcher: def __init__(self): self.watcher = QFileSystemWatcher() self.folders = [] self.files = [] def connect(self): self.watcher.directoryChanged.connect(self.directory_changed) def add_paths(self, list_of_paths): self.watcher.addPaths(list_of_paths) def directory_changed(self, path): for f in listdir(path): complete_path = join(path, f) sub_folder = self.map_path_to_folder_id(path) if not exists(complete_path): continue if complete_path in self.folders or complete_path in self.files: continue if isfile(complete_path): fw = FileWatcher() fw.subfolder = sub_folder fw.base_path = complete_path fw.connect() fw.finished.connect(self.copied_file) self.files.append(fw) else: fw = SubFolderWatcher() fw.base_path = complete_path fw.subfolder = sub_folder fw.connect() fw.finished.connect(self.copied_folder) self.folders.append(fw) def copied_file(self, path): try: i = self.files.index(path) except IndexError: pass else: del self.files[i] def copied_folder(self, path): try: i = self.folders.index(path) except IndexError: pass else: del self.folders[i] @staticmethod def map_path_to_folder_id(path): normalized_path = normpath(path) ending = split(normalized_path)[-1] return ending
def __init__(self, path, max_age): QThread.__init__(self) self.path = path self.max_age = max_age self.files = {} self.file_reg = re.compile('(.+)_\d{8}_\d{6}' + re.escape(os.path.extsep) + 'txt$', re.IGNORECASE) self.qtfw = QFileSystemWatcher() self.qtfw.directoryChanged.connect(self.directory_changed) self.qtfw.addPath(path) self.update_watched_files(path)
def __init__(self): """The constructor, which calls the super-class-contructor (MeshWriter). Adds a filewatcher to replace the saved file from the writer job by our own generated file. """ super().__init__(add_to_recent_files = False) self._write_watcher = QFileSystemWatcher() self._write_watcher.fileChanged.connect(self._writeChanged)
def start(self): # if the file does not exist # create it then watch it if not os.path.exists(self.fnam): with open(self.fnam, 'w') as f: pass # watch file self.w = QFileSystemWatcher([self.fnam]) self.w.fileChanged.connect(self.fileChanged)
class FileWatcher(object): def __init__(self, files=None): self._watcher = QFileSystemWatcher() self._files = list() # List of file(s) to watch self.isWatching = False if files is not None: self.addFile(files) def test(self): file = ['/Users/bitzer/hudat.spec'] self.addFile(file) def addFile(self, files): # Add a file(s) to the watch list # Files needs to be a list, even if a single file # Do it while actively watching? for _f in files: print(_f) self._files.append(_f) def removeFile(self): # Remove a file pass def replaceFile(self, file): # In the (usual) case of a watching a single file, replace it if len(self._files) != 1: print('Only allowed if one file is currently watched') self._files[0] = file def startWatch(self): # Start watching the files self._watcher.addPaths(self._files) self._watcher.fileChanged.connect(self.onChange) self.isWatching = True def stopWatch(self): # Stop Watching the folder self._watcher.removePaths(self._files) self._watcher.fileChanged.disconnect(self.onChange) self.isWatching = False def onChange(self, file): # When a file changes, do something # Which file changed? print('changed ' + file)
class AutocutView(QMainWindow): """Class that displays the view for the autocut feature.""" def __init__(self): """Loads the UI-file and sets up the GUI.""" super(AutocutView, self).__init__() uic.loadUi(Resources.files.autocut_view, self) self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint) self.setFixedSize(self.size()) self.init_stylesheet() "QSS HOT RELOAD" self.__qss_watcher = QFileSystemWatcher() self.__qss_watcher.addPath(Resources.files.qss_dark) self.__qss_watcher.fileChanged.connect(self.update_qss) # centering the window rectangle = self.frameGeometry() center_point = QDesktopWidget().availableGeometry().center() rectangle.moveCenter(center_point) self.move(rectangle.topLeft()) cross_path = Resources.images.cross tick_path = Resources.images.tick self.cross = QPixmap(cross_path).scaledToHeight(50, mode=1) self.tick = QPixmap(tick_path).scaledToHeight(50, mode=1) self.video_image_label.setPixmap(self.cross) self.pdf_image_label.setPixmap(self.cross) def init_stylesheet(self): current_stylesheet = Settings.get_instance().get_settings( ).design.color_theme.current if current_stylesheet == 0: self.setStyleSheet(open(Resources.files.qss_dark, "r").read()) elif current_stylesheet == 1: self.setStyleSheet(open(Resources.files.qss_light, "r").read()) def show(self): """Starts the window normal (not maximized).""" self.showNormal() def change_icon(self, label): """Changes the icon of a label to a tick icon.""" label.setPixmap(self.tick) def update_qss(self): """ Updates the View when stylesheet changed, can be removed in production""" self.setStyleSheet(open(Resources.files.qss_dark, "r").read()) self.__qss_watcher = QFileSystemWatcher() self.__qss_watcher.addPath(Resources.files.qss_dark) self.__qss_watcher.fileChanged.connect(self.update_qss)
def __init__(self, parent): """ Constructor @param parent reference to parent object (Project.Project) """ super(ProjectBrowserModel, self).__init__(parent, nopopulate=True) rootData = self.tr("Name") self.rootItem = BrowserItem(None, rootData) self.rootItem.itemData.append(self.tr("VCS Status")) self.progDir = None self.project = parent self.watchedItems = {} self.watcher = QFileSystemWatcher(self) self.watcher.directoryChanged.connect(self.directoryChanged) self.inRefresh = False self.projectBrowserTypes = { "SOURCES": ProjectBrowserSourceType, "FORMS": ProjectBrowserFormType, "RESOURCES": ProjectBrowserResourceType, "INTERFACES": ProjectBrowserInterfaceType, "PROTOCOLS": ProjectBrowserProtocolsType, "TRANSLATIONS": ProjectBrowserTranslationType, "OTHERS": ProjectBrowserOthersType, } self.colorNames = { "A": "VcsAdded", "M": "VcsModified", "O": "VcsRemoved", "R": "VcsReplaced", "U": "VcsUpdate", "Z": "VcsConflict", } self.itemBackgroundColors = { " ": QColor(), "A": Preferences.getProjectBrowserColour(self.colorNames["A"]), "M": Preferences.getProjectBrowserColour(self.colorNames["M"]), "O": Preferences.getProjectBrowserColour(self.colorNames["O"]), "R": Preferences.getProjectBrowserColour(self.colorNames["R"]), "U": Preferences.getProjectBrowserColour(self.colorNames["U"]), "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]), } self.highLightColor = Preferences.getProjectBrowserColour( "Highlighted") # needed by preferencesChanged() self.vcsStatusReport = {}
def __init__(self): super(SubFolderWatcher, self).__init__() self.base_path = '' self.subfolder = '' self.files = {} self.finished_checking = False self.watcher = QFileSystemWatcher() self.timer = QTimer() self.check_threshold = 3 self.check_number = 0 self.sub_folder = ''
def setup_editor(self): self.rom_stream = tempfile.SpooledTemporaryFile( max_size=self.TEMP_MAX_RAM_USE, mode="w+") self.rom_path = None self.rom_type_sel = self.actionROMAssembly self.lst_parser = None self.rom_watcher = QFileSystemWatcher() self.rom_watcher.fileChanged.connect(self.reload_rom) self.editor_converting = False self.spinBox.setValue(100) self.on_new()
def __init__(self, parent = None): super().__init__(parent) self.mWatchCount = QMap() self.mWatcher = QFileSystemWatcher(self) self.mWatcher.fileChanged.connect(self.onFileChanged) self.mWatcher.directoryChanged.connect(self.onDirectoryChanged)
def test_killed_command(qtbot, tmpdir, py_proc, runner): data_file = tmpdir / "data" watcher = QFileSystemWatcher() watcher.addPath(str(tmpdir)) cmd, args = py_proc( r""" import os import time import sys import json data = { 'pid': os.getpid(), 'text_file': os.environ['QUTE_TEXT'], } # We can't use QUTE_FIFO to transmit the PID because that wouldn't work # on Windows, where QUTE_FIFO is only monitored after the script has # exited. with open(sys.argv[1], 'w') as f: json.dump(data, f) time.sleep(30) """ ) args.append(str(data_file)) with qtbot.waitSignal(watcher.directoryChanged, timeout=10000): runner.prepare_run(cmd, *args) runner.store_text("Hello World") runner.store_html("") # Make sure the PID was written to the file, not just the file created time.sleep(0.5) data = json.load(data_file) with qtbot.waitSignal(runner.finished): os.kill(int(data["pid"]), signal.SIGTERM) assert not os.path.exists(data["text_file"])
def __init__(self, path): QObject.__init__(self) self._contents = None self._watcher = QFileSystemWatcher() self._timer = None self._path = path self._lastEmittedModifiedStatus = None self._lastEmittedRemovedStatus = None self.setPath(path) self.enable()
def __init__(self, parent): """ Constructor @param parent reference to parent object (Project.Project) """ super(ProjectBrowserModel, self).__init__(parent, nopopulate=True) rootData = self.tr("Name") self.rootItem = BrowserItem(None, rootData) self.rootItem.itemData.append(self.tr("VCS Status")) self.progDir = None self.project = parent self.watchedItems = {} self.watcher = QFileSystemWatcher(self) self.watcher.directoryChanged.connect(self.directoryChanged) self.inRefresh = False self.projectBrowserTypes = { "SOURCES": ProjectBrowserSourceType, "FORMS": ProjectBrowserFormType, "RESOURCES": ProjectBrowserResourceType, "INTERFACES": ProjectBrowserInterfaceType, "TRANSLATIONS": ProjectBrowserTranslationType, "OTHERS": ProjectBrowserOthersType, } self.colorNames = { "A": "VcsAdded", "M": "VcsModified", "O": "VcsRemoved", "R": "VcsReplaced", "U": "VcsUpdate", "Z": "VcsConflict", } self.itemBackgroundColors = { " ": QColor(), "A": Preferences.getProjectBrowserColour(self.colorNames["A"]), "M": Preferences.getProjectBrowserColour(self.colorNames["M"]), "O": Preferences.getProjectBrowserColour(self.colorNames["O"]), "R": Preferences.getProjectBrowserColour(self.colorNames["R"]), "U": Preferences.getProjectBrowserColour(self.colorNames["U"]), "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]), } self.highLightColor = \ Preferences.getProjectBrowserColour("Highlighted") # needed by preferencesChanged() self.vcsStatusReport = {}
def __init__(self) -> None: super().__init__() from UM.Scene.SceneNode import SceneNode self._root = SceneNode(name = "Root") self._root.setCalculateBoundingBox(False) self._connectSignalsRoot() self._active_camera = None # type: Optional[Camera] self._ignore_scene_changes = False self._lock = threading.Lock() # Watching file for changes. self._file_watcher = QFileSystemWatcher() self._file_watcher.fileChanged.connect(self._onFileChanged) self._reload_message = None # type: Optional[Message]
class FileSystemWatcher(QObject): fileChanged = pyqtSignal(str) directoryChanged = pyqtSignal(str) def __init__(self, parent = None): super().__init__(parent) self.mWatchCount = QMap() self.mWatcher = QFileSystemWatcher(self) self.mWatcher.fileChanged.connect(self.onFileChanged) self.mWatcher.directoryChanged.connect(self.onDirectoryChanged) def addPath(self, path): # Just silently ignore the request when the file doesn't exist if (not QFile.exists(path)): return entry = self.mWatchCount.find(path) if not entry: self.mWatcher.addPath(path) self.mWatchCount.insert(path, 1) else: # Path is already being watched, increment watch count self.mWatchCount[path] += 1 def removePath(self, path): entry = self.mWatchCount.find(path) if (entry == self.mWatchCount.end()): if (QFile.exists(path)): qWarning("FileSystemWatcher: Path was never added:\n"+path) return # Decrement watch count entry -= 1 self.mWatchCount[path] = entry if (entry == 0): self.mWatchCount.erase(path) self.mWatcher.removePath(path) def onFileChanged(self, path): # If the file was replaced, the watcher is automatically removed and needs # to be re-added to keep watching it for changes. This happens commonly # with applications that do atomic saving. if (not self.mWatcher.files().__contains__(path)): if (QFile.exists(path)): self.mWatcher.addPath(path) self.fileChanged.emit(path) def onDirectoryChanged(self, path): self.directoryChanged.emit(path)
def load(self, filename): # Load HDR image with FreeImage image = self.loadHDRImage(filename) if image is None: return False # Change window size self.sizeChanged.emit(image.width(), image.height()) # Set the image to imageLabel self.setPixmap(QPixmap.fromImage(image)) self.adjustSize() # Begin to watch modification self.watcher = QFileSystemWatcher() self.watcher.addPath(filename) self.watcher.fileChanged.connect(self.onFileChanged) return True
def main(argv): signal.signal(signal.SIGINT, signal.SIG_DFL) app = QCoreApplication([]) watcher = QFileSystemWatcher() print("Watching /tmp/") watcher.addPath("/tmp/") watcher.addPath("/tmp/foo") # Files have to be watched specifically for this to trigger. # Deleting and recreating a file makes this no longer trigger. watcher.fileChanged.connect(file_changed) # This triggers on file creation and deletion watcher.directoryChanged.connect(directory_changed) print("files:", watcher.files()) print("directories:", watcher.directories()) sys.exit(app.exec())
class ExecuteOptionsPlugin(QWidget, Plugin): """ Handles setting the various arguments for running. Signals: executableChanged(str): Path of the new executable is emitted when changed executableInfoChanged(ExecutableInfo): Emitted when the executable path is changed workingDirChanged(str): Path of the current directory is changed """ executableChanged = pyqtSignal(str) executableInfoChanged = pyqtSignal(ExecutableInfo) workingDirChanged = pyqtSignal(str) useTestObjectsChanged = pyqtSignal(bool) def __init__(self, **kwds): super(ExecuteOptionsPlugin, self).__init__(**kwds) self._preferences.addInt("execute/maxRecentWorkingDirs", "Max recent working directories", 10, 1, 50, "Set the maximum number of recent working directories that have been used.", ) self._preferences.addInt("execute/maxRecentExes", "Max recent executables", 10, 1, 50, "Set the maximum number of recent executables that have been used.", ) self._preferences.addInt("execute/maxRecentArgs", "Max recent command line arguments", 10, 1, 50, "Set the maximum number of recent command line arguments that have been used.", ) self._preferences.addBool("execute/allowTestObjects", "Allow using test objects", False, "Allow using test objects by default", ) self._preferences.addBool("execute/mpiEnabled", "Enable MPI by default", False, "Set the MPI checkbox on by default", ) self._preferences.addString("execute/mpiArgs", "Default mpi command", "mpiexec -n 2", "Set the default MPI command to run", ) self._preferences.addBool("execute/threadsEnabled", "Enable threads by default", False, "Set the threads checkbox on by default", ) self._preferences.addString("execute/threadsArgs", "Default threads arguments", "--n-threads=2", "Set the default threads arguments", ) self.all_exe_layout = WidgetUtils.addLayout(grid=True) self.setLayout(self.all_exe_layout) self.working_label = WidgetUtils.addLabel(None, self, "Working Directory") self.all_exe_layout.addWidget(self.working_label, 0, 0) self.choose_working_button = WidgetUtils.addButton(None, self, "Choose", self._chooseWorkingDir) self.all_exe_layout.addWidget(self.choose_working_button, 0, 1) self.working_line = WidgetUtils.addLineEdit(None, self, None, readonly=True) self.working_line.setText(os.getcwd()) self.all_exe_layout.addWidget(self.working_line, 0, 2) self.exe_label = WidgetUtils.addLabel(None, self, "Executable") self.all_exe_layout.addWidget(self.exe_label, 1, 0) self.choose_exe_button = WidgetUtils.addButton(None, self, "Choose", self._chooseExecutable) self.all_exe_layout.addWidget(self.choose_exe_button, 1, 1) self.exe_line = WidgetUtils.addLineEdit(None, self, None, readonly=True) self.all_exe_layout.addWidget(self.exe_line, 1, 2) self.args_label = WidgetUtils.addLabel(None, self, "Extra Arguments") self.all_exe_layout.addWidget(self.args_label, 2, 0) self.args_line = WidgetUtils.addLineEdit(None, self, None) self.all_exe_layout.addWidget(self.args_line, 2, 2) self.test_label = WidgetUtils.addLabel(None, self, "Allow test objects") self.all_exe_layout.addWidget(self.test_label, 3, 0) self.test_checkbox = WidgetUtils.addCheckbox(None, self, "", self._allowTestObjects) self.test_checkbox.setChecked(self._preferences.value("execute/allowTestObjects")) self.all_exe_layout.addWidget(self.test_checkbox, 3, 1, alignment=Qt.AlignHCenter) self.mpi_label = WidgetUtils.addLabel(None, self, "Use MPI") self.all_exe_layout.addWidget(self.mpi_label, 4, 0) self.mpi_checkbox = WidgetUtils.addCheckbox(None, self, "", None) self.mpi_checkbox.setChecked(self._preferences.value("execute/mpiEnabled")) self.all_exe_layout.addWidget(self.mpi_checkbox, 4, 1, alignment=Qt.AlignHCenter) self.mpi_line = WidgetUtils.addLineEdit(None, self, None) self.mpi_line.setText(self._preferences.value("execute/mpiArgs")) self.mpi_line.cursorPositionChanged.connect(self._mpiLineCursorChanged) self.all_exe_layout.addWidget(self.mpi_line, 4, 2) self.threads_label = WidgetUtils.addLabel(None, self, "Use Threads") self.all_exe_layout.addWidget(self.threads_label, 5, 0) self.threads_checkbox = WidgetUtils.addCheckbox(None, self, "", None) self.threads_checkbox.setChecked(self._preferences.value("execute/threadsEnabled")) self.all_exe_layout.addWidget(self.threads_checkbox, 5, 1, alignment=Qt.AlignHCenter) self.threads_line = WidgetUtils.addLineEdit(None, self, None) self.threads_line.setText(self._preferences.value("execute/threadsArgs")) self.threads_line.cursorPositionChanged.connect(self._threadsLineCursorChanged) self.all_exe_layout.addWidget(self.threads_line, 5, 2) self.csv_label = WidgetUtils.addLabel(None, self, "Postprocessor CSV Output") self.all_exe_layout.addWidget(self.csv_label, 6, 0) self.csv_checkbox = WidgetUtils.addCheckbox(None, self, "", None) self.all_exe_layout.addWidget(self.csv_checkbox, 6, 1, alignment=Qt.AlignHCenter) self.csv_checkbox.setCheckState(Qt.Checked) self.recover_label = WidgetUtils.addLabel(None, self, "Recover") self.all_exe_layout.addWidget(self.recover_label, 7, 0) self.recover_checkbox = WidgetUtils.addCheckbox(None, self, "", None) self.all_exe_layout.addWidget(self.recover_checkbox, 7, 1, alignment=Qt.AlignHCenter) self._recent_exe_menu = None self._recent_working_menu = None self._recent_args_menu = None self._exe_watcher = QFileSystemWatcher() self._exe_watcher.fileChanged.connect(self.setExecutablePath) self._loading_dialog = QMessageBox(parent=self) self._loading_dialog.setWindowTitle("Loading executable") self._loading_dialog.setStandardButtons(QMessageBox.NoButton) # get rid of the OK button self._loading_dialog.setWindowModality(Qt.ApplicationModal) self._loading_dialog.setIcon(QMessageBox.Information) self._loading_dialog.setText("Loading executable") self.setup() def setExecutablePath(self, app_path): """ The user select a new executable path. Input: app_path: The path of the executable. """ if not app_path: return self._loading_dialog.setInformativeText(app_path) self._loading_dialog.show() self._loading_dialog.raise_() QApplication.processEvents() app_info = ExecutableInfo() app_info.setPath(app_path, self.test_checkbox.isChecked()) QApplication.processEvents() if app_info.valid(): self.exe_line.setText(app_path) self.executableInfoChanged.emit(app_info) self.executableChanged.emit(app_path) files = self._exe_watcher.files() if files: self._exe_watcher.removePaths(files) self._exe_watcher.addPath(app_path) self._updateRecentExe(app_path, not app_info.valid()) self._loading_dialog.hide() def _chooseExecutable(self): """ Open a dialog to allow the user to choose an executable. """ #FIXME: QFileDialog seems to be a bit broken. Using # .setFilter() to filter only executable files doesn't # seem to work. Setting a QSortFilterProxyModel doesn't # seem to work either. # So just use the static method. exe_name, other = QFileDialog.getOpenFileName(self, "Chooose executable") self.setExecutablePath(exe_name) def _allowTestObjects(self): """ Reload the ExecutableInfo based on whether we are allowing test objects or not. """ self.useTestObjectsChanged.emit(self.test_checkbox.isChecked()) self.setExecutablePath(self.exe_line.text()) def _workingDirChanged(self): """ Slot called when working directory changed. """ working = str(self.working_line.text()) self.setWorkingDir(working) def _chooseWorkingDir(self): """ Open dialog to choose a current working directory. """ dirname = QFileDialog.getExistingDirectory(self, "Choose directory") self.setWorkingDir(dirname) def setWorkingDir(self, dir_name): """ Sets the working directory. Input: dir_name: The path of the working directory. """ if not dir_name: return old_dirname = str(self.working_line.text()) try: os.chdir(dir_name) self.working_line.setText(dir_name) if old_dirname != dir_name: self.workingDirChanged.emit(dir_name) self._updateRecentWorkingDir(dir_name) except OSError: mooseutils.mooseError("Invalid directory %s" % dir_name, dialog=True) self._updateRecentWorkingDir(dir_name, True) def _setExecutableArgs(self, args): """ Set the executable arguments. Input: args: str: A string of all the arguments. """ self.args_line.setText(args) def buildCommand(self, input_file): cmd, args = self.buildCommandWithNoInputFile() args.append("-i") args.append(os.path.relpath(input_file)) return cmd, args def buildCommandWithNoInputFile(self): """ Builds the full command line with arguments. Return: <string of command to run>, <list of arguments> """ cmd = "" args = [] if self.mpi_checkbox.isChecked(): mpi_args = shlex.split(str(self.mpi_line.text())) if mpi_args: cmd = mpi_args[0] args = mpi_args[1:] args.append(str(self.exe_line.text())) if not cmd: cmd = str(self.exe_line.text()) args += shlex.split(str(self.args_line.text())) if self.recover_checkbox.isChecked(): args.append("--recover") if self.csv_checkbox.isChecked(): #args.append("--no-color") args.append("Outputs/csv=true") if self.threads_checkbox.isChecked(): args += shlex.split(str(self.threads_line.text())) return cmd, args def _updateRecentExe(self, path, remove=False): """ Updates the recently used menu with the current executable """ if self._recent_exe_menu: abs_path = os.path.normcase(os.path.abspath(path)) if remove: self._recent_exe_menu.removeEntry(abs_path) else: self._recent_exe_menu.update(abs_path) def _updateRecentWorkingDir(self, path, remove=False): """ Updates the recently used menu with the current executable """ full_path = os.path.abspath(path) if self._recent_working_menu: if remove: self._recent_working_menu.removeEntry(full_path) else: self._recent_working_menu.update(full_path) def onPreferencesSaved(self): self._recent_args_menu.updateRecentlyOpened() self._recent_working_menu.updateRecentlyOpened() self._recent_exe_menu.updateRecentlyOpened() def addToMenu(self, menu): """ Adds menu entries specific to the Arguments to the menubar. """ workingMenu = menu.addMenu("Recent &working dirs") self._recent_working_menu = RecentlyUsedMenu(workingMenu, "execute/recentWorkingDirs", "execute/maxRecentWorkingDirs", 20, ) self._recent_working_menu.selected.connect(self.setWorkingDir) self._workingDirChanged() exeMenu = menu.addMenu("Recent &executables") self._recent_exe_menu = RecentlyUsedMenu(exeMenu, "execute/recentExes", "execute/maxRecentExes", 20, ) self._recent_exe_menu.selected.connect(self.setExecutablePath) argsMenu = menu.addMenu("Recent &arguments") self._recent_args_menu = RecentlyUsedMenu(argsMenu, "execute/recentArgs", "execute/maxRecentArgs", 20, ) self._recent_args_menu.selected.connect(self._setExecutableArgs) def clearRecentlyUsed(self): if self._recent_args_menu: self._recent_args_menu.clearValues() self._recent_working_menu.clearValues() self._recent_exe_menu.clearValues() self._workingDirChanged() def _mpiLineCursorChanged(self, old, new): self.mpi_checkbox.setChecked(True) def _threadsLineCursorChanged(self, old, new): self.threads_checkbox.setChecked(True)
class ReTextWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.resize(950, 700) screenRect = QDesktopWidget().screenGeometry() if globalSettings.windowGeometry: self.restoreGeometry(globalSettings.windowGeometry) else: self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2) if not screenRect.contains(self.geometry()): self.showMaximized() if globalSettings.iconTheme: QIcon.setThemeName(globalSettings.iconTheme) if QIcon.themeName() in ('hicolor', ''): if not QFile.exists(icon_path + 'document-new.png'): QIcon.setThemeName(get_icon_theme()) if QFile.exists(icon_path+'retext.png'): self.setWindowIcon(QIcon(icon_path+'retext.png')) elif QFile.exists('/usr/share/pixmaps/retext.png'): self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png')) else: self.setWindowIcon(QIcon.fromTheme('retext', QIcon.fromTheme('accessories-text-editor'))) self.tabWidget = QTabWidget(self) self.initTabWidget() self.setCentralWidget(self.tabWidget) self.tabWidget.currentChanged.connect(self.changeIndex) self.tabWidget.tabCloseRequested.connect(self.closeTab) toolBar = QToolBar(self.tr('File toolbar'), self) self.addToolBar(Qt.TopToolBarArea, toolBar) self.editBar = QToolBar(self.tr('Edit toolbar'), self) self.addToolBar(Qt.TopToolBarArea, self.editBar) self.searchBar = QToolBar(self.tr('Search toolbar'), self) self.addToolBar(Qt.BottomToolBarArea, self.searchBar) toolBar.setVisible(not globalSettings.hideToolBar) self.editBar.setVisible(not globalSettings.hideToolBar) self.actionNew = self.act(self.tr('New'), 'document-new', self.createNew, shct=QKeySequence.New) self.actionNew.setPriority(QAction.LowPriority) self.actionOpen = self.act(self.tr('Open'), 'document-open', self.openFile, shct=QKeySequence.Open) self.actionOpen.setPriority(QAction.LowPriority) self.actionSetEncoding = self.act(self.tr('Set encoding'), trig=self.showEncodingDialog) self.actionSetEncoding.setEnabled(False) self.actionReload = self.act(self.tr('Reload'), 'view-refresh', lambda: self.currentTab.readTextFromFile()) self.actionReload.setEnabled(False) self.actionSave = self.act(self.tr('Save'), 'document-save', self.saveFile, shct=QKeySequence.Save) self.actionSave.setEnabled(False) self.actionSave.setPriority(QAction.LowPriority) self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as', self.saveFileAs, shct=QKeySequence.SaveAs) self.actionNextTab = self.act(self.tr('Next tab'), 'go-next', lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown) self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous', lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp) self.actionPrint = self.act(self.tr('Print'), 'document-print', self.printFile, shct=QKeySequence.Print) self.actionPrint.setPriority(QAction.LowPriority) self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview', self.printPreview) self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml) self.actionChangeEditorFont = self.act(self.tr('Change editor font'), trig=self.changeEditorFont) self.actionChangePreviewFont = self.act(self.tr('Change preview font'), trig=self.changePreviewFont) self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find) self.actionSearch.setCheckable(True) self.actionSearch.triggered[bool].connect(self.searchBar.setVisible) self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged) self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E, trigbool=self.preview) if QIcon.hasThemeIcon('document-preview'): self.actionPreview.setIcon(QIcon.fromTheme('document-preview')) elif QIcon.hasThemeIcon('preview-file'): self.actionPreview.setIcon(QIcon.fromTheme('preview-file')) elif QIcon.hasThemeIcon('x-office-document'): self.actionPreview.setIcon(QIcon.fromTheme('x-office-document')) else: self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png')) self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L, trigbool=self.enableLivePreview) menuPreview = QMenu() menuPreview.addAction(self.actionLivePreview) self.actionPreview.setMenu(menuPreview) self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T, trigbool=lambda x: self.currentTab.editBox.enableTableMode(x)) if ReTextFakeVimHandler: self.actionFakeVimMode = self.act(self.tr('FakeVim mode'), shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode) if globalSettings.useFakeVim: self.actionFakeVimMode.setChecked(True) self.enableFakeVimMode(True) self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen', shct=Qt.Key_F11, trigbool=self.enableFullScreen) self.actionFullScreen.setPriority(QAction.LowPriority) self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system', trig=self.openConfigDialog) self.actionConfig.setMenuRole(QAction.PreferencesRole) self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml) self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf) self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf) self.getExportExtensionsList() self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit) self.actionQuit.setMenuRole(QAction.QuitRole) self.actionQuit.triggered.connect(self.close) self.actionUndo = self.act(self.tr('Undo'), 'edit-undo', lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo) self.actionRedo = self.act(self.tr('Redo'), 'edit-redo', lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo) self.actionCopy = self.act(self.tr('Copy'), 'edit-copy', lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy) self.actionCut = self.act(self.tr('Cut'), 'edit-cut', lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut) self.actionPaste = self.act(self.tr('Paste'), 'edit-paste', lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste) self.actionUndo.setEnabled(False) self.actionRedo.setEnabled(False) self.actionCopy.setEnabled(False) self.actionCut.setEnabled(False) qApp = QApplication.instance() qApp.clipboard().dataChanged.connect(self.clipboardDataChanged) self.clipboardDataChanged() if enchant_available: self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck) self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale) self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit) self.actionWebKit.setChecked(globalSettings.useWebKit) self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir) self.actionFind = self.act(self.tr('Next'), 'go-next', self.find, shct=QKeySequence.FindNext) self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous', lambda: self.find(back=True), shct=QKeySequence.FindPrevious) self.actionCloseSearch = self.act(self.tr('Close'), 'window-close', lambda: self.searchBar.setVisible(False)) self.actionCloseSearch.setPriority(QAction.LowPriority) self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp) self.aboutWindowTitle = self.tr('About ReText') self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog) self.actionAbout.setMenuRole(QAction.AboutRole) self.actionAboutQt = self.act(self.tr('About Qt')) self.actionAboutQt.setMenuRole(QAction.AboutQtRole) self.actionAboutQt.triggered.connect(qApp.aboutQt) availableMarkups = markups.get_available_markups() if not availableMarkups: print('Warning: no markups are available!') self.defaultMarkup = availableMarkups[0] if availableMarkups else None if globalSettings.defaultMarkup: mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup) if mc and mc.available(): self.defaultMarkup = mc if len(availableMarkups) > 1: self.chooseGroup = QActionGroup(self) markupActions = [] for markup in availableMarkups: markupAction = self.act(markup.name, trigbool=self.markupFunction(markup)) if markup == self.defaultMarkup: markupAction.setChecked(True) self.chooseGroup.addAction(markupAction) markupActions.append(markupAction) self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold, trig=lambda: self.insertFormatting('bold')) self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic, trig=lambda: self.insertFormatting('italic')) self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline, trig=lambda: self.insertFormatting('underline')) self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering', 'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote') self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr', 'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo', 'rarr', 'rsquo', 'times') self.formattingBox = QComboBox(self.editBar) self.formattingBox.addItem(self.tr('Formatting')) self.formattingBox.addItems(self.usefulTags) self.formattingBox.activated[str].connect(self.insertFormatting) self.symbolBox = QComboBox(self.editBar) self.symbolBox.addItem(self.tr('Symbols')) self.symbolBox.addItems(self.usefulChars) self.symbolBox.activated.connect(self.insertSymbol) self.updateStyleSheet() menubar = self.menuBar() menuFile = menubar.addMenu(self.tr('File')) menuEdit = menubar.addMenu(self.tr('Edit')) menuHelp = menubar.addMenu(self.tr('Help')) menuFile.addAction(self.actionNew) menuFile.addAction(self.actionOpen) self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent')) self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles) menuFile.addAction(self.actionShow) menuFile.addAction(self.actionSetEncoding) menuFile.addAction(self.actionReload) menuFile.addSeparator() menuFile.addAction(self.actionSave) menuFile.addAction(self.actionSaveAs) menuFile.addSeparator() menuFile.addAction(self.actionNextTab) menuFile.addAction(self.actionPrevTab) menuFile.addSeparator() menuExport = menuFile.addMenu(self.tr('Export')) menuExport.addAction(self.actionSaveHtml) menuExport.addAction(self.actionOdf) menuExport.addAction(self.actionPdf) if self.extensionActions: menuExport.addSeparator() for action, mimetype in self.extensionActions: menuExport.addAction(action) menuExport.aboutToShow.connect(self.updateExtensionsVisibility) menuFile.addAction(self.actionPrint) menuFile.addAction(self.actionPrintPreview) menuFile.addSeparator() menuFile.addAction(self.actionQuit) menuEdit.addAction(self.actionUndo) menuEdit.addAction(self.actionRedo) menuEdit.addSeparator() menuEdit.addAction(self.actionCut) menuEdit.addAction(self.actionCopy) menuEdit.addAction(self.actionPaste) menuEdit.addSeparator() if enchant_available: menuSC = menuEdit.addMenu(self.tr('Spell check')) menuSC.addAction(self.actionEnableSC) menuSC.addAction(self.actionSetLocale) menuEdit.addAction(self.actionSearch) menuEdit.addAction(self.actionChangeEditorFont) menuEdit.addAction(self.actionChangePreviewFont) menuEdit.addSeparator() if len(availableMarkups) > 1: self.menuMode = menuEdit.addMenu(self.tr('Default markup')) for markupAction in markupActions: self.menuMode.addAction(markupAction) menuFormat = menuEdit.addMenu(self.tr('Formatting')) menuFormat.addAction(self.actionBold) menuFormat.addAction(self.actionItalic) menuFormat.addAction(self.actionUnderline) menuEdit.addAction(self.actionWebKit) menuEdit.addSeparator() menuEdit.addAction(self.actionViewHtml) menuEdit.addAction(self.actionPreview) menuEdit.addAction(self.actionTableMode) if ReTextFakeVimHandler: menuEdit.addAction(self.actionFakeVimMode) menuEdit.addSeparator() menuEdit.addAction(self.actionFullScreen) menuEdit.addAction(self.actionConfig) menuHelp.addAction(self.actionHelp) menuHelp.addSeparator() menuHelp.addAction(self.actionAbout) menuHelp.addAction(self.actionAboutQt) toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toolBar.addAction(self.actionNew) toolBar.addSeparator() toolBar.addAction(self.actionOpen) toolBar.addAction(self.actionSave) toolBar.addAction(self.actionPrint) toolBar.addSeparator() toolBar.addAction(self.actionPreview) toolBar.addAction(self.actionFullScreen) self.editBar.addAction(self.actionUndo) self.editBar.addAction(self.actionRedo) self.editBar.addSeparator() self.editBar.addAction(self.actionCut) self.editBar.addAction(self.actionCopy) self.editBar.addAction(self.actionPaste) self.editBar.addSeparator() self.editBar.addWidget(self.formattingBox) self.editBar.addWidget(self.symbolBox) self.searchEdit = QLineEdit(self.searchBar) self.searchEdit.setPlaceholderText(self.tr('Search')) self.searchEdit.returnPressed.connect(self.find) self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar) self.searchBar.addWidget(self.searchEdit) self.searchBar.addSeparator() self.searchBar.addWidget(self.csBox) self.searchBar.addAction(self.actionFindPrev) self.searchBar.addAction(self.actionFind) self.searchBar.addAction(self.actionCloseSearch) self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.searchBar.setVisible(False) self.autoSaveEnabled = globalSettings.autoSave if self.autoSaveEnabled: timer = QTimer(self) timer.start(60000) timer.timeout.connect(self.saveAll) self.ind = None if enchant_available: self.sl = globalSettings.spellCheckLocale if self.sl: try: enchant.Dict(self.sl) except Exception as e: print(e, file=sys.stderr) self.sl = None if globalSettings.spellCheck: self.actionEnableSC.setChecked(True) self.fileSystemWatcher = QFileSystemWatcher() self.fileSystemWatcher.fileChanged.connect(self.fileChanged) def iterateTabs(self): for i in range(self.tabWidget.count()): yield self.tabWidget.widget(i).tab def updateStyleSheet(self): if globalSettings.styleSheet: sheetfile = QFile(globalSettings.styleSheet) sheetfile.open(QIODevice.ReadOnly) self.ss = QTextStream(sheetfile).readAll() sheetfile.close() else: palette = QApplication.palette() self.ss = 'html { color: %s; }\n' % palette.color(QPalette.WindowText).name() self.ss += 'td, th { border: 1px solid #c3c3c3; padding: 0 3px 0 3px; }\n' self.ss += 'table { border-collapse: collapse; }\n' def initTabWidget(self): def dragEnterEvent(e): e.acceptProposedAction() def dropEvent(e): fn = bytes(e.mimeData().data('text/plain')).decode().rstrip() if fn.startswith('file:'): fn = QUrl(fn).toLocalFile() self.openFileWrapper(fn) self.tabWidget.setTabsClosable(True) self.tabWidget.setAcceptDrops(True) self.tabWidget.setMovable(True) self.tabWidget.dragEnterEvent = dragEnterEvent self.tabWidget.dropEvent = dropEvent def act(self, name, icon=None, trig=None, trigbool=None, shct=None): if not isinstance(shct, QKeySequence): shct = QKeySequence(shct) if icon: action = QAction(self.actIcon(icon), name, self) else: action = QAction(name, self) if trig: action.triggered.connect(trig) elif trigbool: action.setCheckable(True) action.triggered[bool].connect(trigbool) if shct: action.setShortcut(shct) return action def actIcon(self, name): return QIcon.fromTheme(name, QIcon(icon_path+name+'.png')) def printError(self): import traceback print('Exception occured while parsing document:', file=sys.stderr) traceback.print_exc() def createTab(self, fileName): self.currentTab = ReTextTab(self, fileName, previewState=int(globalSettings.livePreviewByDefault)) self.tabWidget.addTab(self.currentTab.getSplitter(), self.tr("New document")) def closeTab(self, ind): if self.maybeSave(ind): if self.tabWidget.count() == 1: self.createTab("") currentWidget = self.tabWidget.widget(ind) if currentWidget.tab.fileName: self.fileSystemWatcher.removePath(currentWidget.tab.fileName) del currentWidget.tab self.tabWidget.removeTab(ind) def docTypeChanged(self): markupClass = self.currentTab.getMarkupClass() if type(self.currentTab.markup) != markupClass: self.currentTab.setMarkupClass(markupClass) self.currentTab.updatePreviewBox() dtMarkdown = (markupClass == markups.MarkdownMarkup) dtMkdOrReST = dtMarkdown or (markupClass == markups.ReStructuredTextMarkup) self.formattingBox.setEnabled(dtMarkdown) self.symbolBox.setEnabled(dtMarkdown) self.actionUnderline.setEnabled(dtMarkdown) self.actionBold.setEnabled(dtMkdOrReST) self.actionItalic.setEnabled(dtMkdOrReST) canReload = bool(self.currentTab.fileName) and not self.autoSaveActive() self.actionSetEncoding.setEnabled(canReload) self.actionReload.setEnabled(canReload) def changeIndex(self, ind): self.currentTab = self.tabWidget.currentWidget().tab editBox = self.currentTab.editBox previewState = self.currentTab.previewState self.actionUndo.setEnabled(editBox.document().isUndoAvailable()) self.actionRedo.setEnabled(editBox.document().isRedoAvailable()) self.actionCopy.setEnabled(editBox.textCursor().hasSelection()) self.actionCut.setEnabled(editBox.textCursor().hasSelection()) self.actionPreview.setChecked(previewState >= PreviewLive) self.actionLivePreview.setChecked(previewState == PreviewLive) self.actionTableMode.setChecked(editBox.tableModeEnabled) self.editBar.setEnabled(previewState < PreviewNormal) self.ind = ind if self.currentTab.fileName: self.setCurrentFile() else: self.setWindowTitle(self.tr('New document') + '[*]') self.docTypeChanged() self.modificationChanged(editBox.document().isModified()) editBox.setFocus(Qt.OtherFocusReason) def changeEditorFont(self): font, ok = QFontDialog.getFont(globalSettings.editorFont, self) if ok: globalSettings.editorFont = font for tab in self.iterateTabs(): tab.editBox.updateFont() def changePreviewFont(self): font, ok = QFontDialog.getFont(globalSettings.font, self) if ok: globalSettings.font = font for tab in self.iterateTabs(): tab.updatePreviewBox() def preview(self, viewmode): self.currentTab.previewState = viewmode * 2 self.actionLivePreview.setChecked(False) self.editBar.setDisabled(viewmode) self.currentTab.updateBoxesVisibility() if viewmode: self.currentTab.updatePreviewBox() def enableLivePreview(self, livemode): self.currentTab.previewState = int(livemode) self.actionPreview.setChecked(livemode) self.editBar.setEnabled(True) self.currentTab.updateBoxesVisibility() if livemode: self.currentTab.updatePreviewBox() def enableWebKit(self, enable): globalSettings.useWebKit = enable for i in range(self.tabWidget.count()): splitter = self.tabWidget.widget(i) tab = splitter.tab tab.previewBox.disconnectExternalSignals() tab.previewBox.setParent(None) tab.previewBox.deleteLater() tab.previewBox = tab.createPreviewBox(tab.editBox) tab.previewBox.setMinimumWidth(125) splitter.addWidget(tab.previewBox) splitter.setSizes((50, 50)) tab.updatePreviewBox() tab.updateBoxesVisibility() def enableCopy(self, copymode): self.actionCopy.setEnabled(copymode) self.actionCut.setEnabled(copymode) def enableFullScreen(self, yes): if yes: self.showFullScreen() else: self.showNormal() def openConfigDialog(self): dlg = ConfigDialog(self) dlg.setWindowTitle(self.tr('Preferences')) dlg.show() def enableFakeVimMode(self, yes): globalSettings.useFakeVim = yes if yes: FakeVimMode.init(self) for tab in self.iterateTabs(): tab.installFakeVimHandler() else: FakeVimMode.exit(self) def enableSpellCheck(self, yes): if yes: self.setAllDictionaries(enchant.Dict(self.sl or None)) else: self.setAllDictionaries(None) globalSettings.spellCheck = yes def setAllDictionaries(self, dictionary): for tab in self.iterateTabs(): hl = tab.highlighter hl.dictionary = dictionary hl.rehighlight() def changeLocale(self): if self.sl: localedlg = LocaleDialog(self, defaultText=self.sl) else: localedlg = LocaleDialog(self) if localedlg.exec() != QDialog.Accepted: return sl = localedlg.localeEdit.text() setdefault = localedlg.checkBox.isChecked() if sl: try: sl = str(sl) enchant.Dict(sl) except Exception as e: QMessageBox.warning(self, '', str(e)) else: self.sl = sl self.enableSpellCheck(self.actionEnableSC.isChecked()) else: self.sl = None self.enableSpellCheck(self.actionEnableSC.isChecked()) if setdefault: globalSettings.spellCheckLocale = sl def searchBarVisibilityChanged(self, visible): self.actionSearch.setChecked(visible) if visible: self.searchEdit.setFocus(Qt.ShortcutFocusReason) def find(self, back=False): flags = QTextDocument.FindFlags() if back: flags |= QTextDocument.FindBackward if self.csBox.isChecked(): flags |= QTextDocument.FindCaseSensitively text = self.searchEdit.text() editBox = self.currentTab.editBox cursor = editBox.textCursor() newCursor = editBox.document().find(text, cursor, flags) if not newCursor.isNull(): editBox.setTextCursor(newCursor) return self.setSearchEditColor(True) cursor.movePosition(QTextCursor.End if back else QTextCursor.Start) newCursor = editBox.document().find(text, cursor, flags) if not newCursor.isNull(): editBox.setTextCursor(newCursor) return self.setSearchEditColor(True) self.setSearchEditColor(False) def setSearchEditColor(self, found): palette = self.searchEdit.palette() palette.setColor(QPalette.Active, QPalette.Base, Qt.white if found else QColor(255, 102, 102)) self.searchEdit.setPalette(palette) def showInDir(self): if self.currentTab.fileName: path = QFileInfo(self.currentTab.fileName).path() QDesktopServices.openUrl(QUrl.fromLocalFile(path)) else: QMessageBox.warning(self, '', self.tr("Please, save the file somewhere.")) def setCurrentFile(self): self.setWindowTitle("") self.tabWidget.setTabText(self.ind, self.currentTab.getDocumentTitle(baseName=True)) self.tabWidget.setTabToolTip(self.ind, self.currentTab.fileName or '') self.setWindowFilePath(self.currentTab.fileName) files = readListFromSettings("recentFileList") while self.currentTab.fileName in files: files.remove(self.currentTab.fileName) files.insert(0, self.currentTab.fileName) if len(files) > 10: del files[10:] writeListToSettings("recentFileList", files) QDir.setCurrent(QFileInfo(self.currentTab.fileName).dir().path()) self.docTypeChanged() def createNew(self, text=None): self.createTab("") self.ind = self.tabWidget.count()-1 self.tabWidget.setCurrentIndex(self.ind) if text: self.currentTab.editBox.textCursor().insertText(text) def switchTab(self, shift=1): self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count()) def updateRecentFiles(self): self.menuRecentFiles.clear() self.recentFilesActions = [] filesOld = readListFromSettings("recentFileList") files = [] for f in filesOld: if QFile.exists(f): files.append(f) self.recentFilesActions.append(self.act(f, trig=self.openFunction(f))) writeListToSettings("recentFileList", files) for action in self.recentFilesActions: self.menuRecentFiles.addAction(action) def markupFunction(self, markup): return lambda: self.setDefaultMarkup(markup) def openFunction(self, fileName): return lambda: self.openFileWrapper(fileName) def extensionFunction(self, data): return lambda: \ self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension']) def getExportExtensionsList(self): extensions = [] for extsprefix in datadirs: extsdir = QDir(extsprefix+'/export-extensions/') if extsdir.exists(): for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'], QDir.Files | QDir.Readable): extensions.append(self.readExtension(fileInfo.filePath())) locale = QLocale.system().name() self.extensionActions = [] for extension in extensions: try: if ('Name[%s]' % locale) in extension: name = extension['Name[%s]' % locale] elif ('Name[%s]' % locale.split('_')[0]) in extension: name = extension['Name[%s]' % locale.split('_')[0]] else: name = extension['Name'] data = {} for prop in ('FileFilter', 'DefaultExtension', 'Exec'): if 'X-ReText-'+prop in extension: data[prop] = extension['X-ReText-'+prop] elif prop in extension: data[prop] = extension[prop] else: data[prop] = '' action = self.act(name, trig=self.extensionFunction(data)) if 'Icon' in extension: action.setIcon(self.actIcon(extension['Icon'])) mimetype = extension['MimeType'] if 'MimeType' in extension else None except KeyError: print('Failed to parse extension: Name is required', file=sys.stderr) else: self.extensionActions.append((action, mimetype)) def updateExtensionsVisibility(self): markupClass = self.currentTab.getMarkupClass() for action in self.extensionActions: if markupClass is None: action[0].setEnabled(False) continue mimetype = action[1] if mimetype == None: enabled = True elif markupClass == markups.MarkdownMarkup: enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown")) elif markupClass == markups.ReStructuredTextMarkup: enabled = (mimetype in ("text/x-retext-rst", "text/x-rst")) else: enabled = False action[0].setEnabled(enabled) def readExtension(self, fileName): extFile = QFile(fileName) extFile.open(QIODevice.ReadOnly) extension = {} stream = QTextStream(extFile) while not stream.atEnd(): line = stream.readLine() if '=' in line: index = line.index('=') extension[line[:index].rstrip()] = line[index+1:].lstrip() extFile.close() return extension def openFile(self): supportedExtensions = ['.txt'] for markup in markups.get_all_markups(): supportedExtensions += markup.file_extensions fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;' fileNames = QFileDialog.getOpenFileNames(self, self.tr("Select one or several files to open"), "", self.tr("Supported files") + fileFilter + self.tr("All files (*)")) for fileName in fileNames[0]: self.openFileWrapper(fileName) def openFileWrapper(self, fileName): if not fileName: return fileName = QFileInfo(fileName).canonicalFilePath() exists = False for i, tab in enumerate(self.iterateTabs()): if tab.fileName == fileName: exists = True ex = i if exists: self.tabWidget.setCurrentIndex(ex) elif QFile.exists(fileName): noEmptyTab = ( (self.ind is None) or self.currentTab.fileName or self.currentTab.editBox.toPlainText() or self.currentTab.editBox.document().isModified() ) if noEmptyTab: self.createTab(fileName) self.ind = self.tabWidget.count()-1 self.tabWidget.setCurrentIndex(self.ind) if fileName: self.fileSystemWatcher.addPath(fileName) self.currentTab.fileName = fileName self.currentTab.readTextFromFile() editBox = self.currentTab.editBox self.setCurrentFile() self.setWindowModified(editBox.document().isModified()) def showEncodingDialog(self): if not self.maybeSave(self.ind): return encoding, ok = QInputDialog.getItem(self, '', self.tr('Select file encoding from the list:'), [bytes(b).decode() for b in QTextCodec.availableCodecs()], 0, False) if ok: self.currentTab.readTextFromFile(encoding) def saveFile(self): self.saveFileMain(dlg=False) def saveFileAs(self): self.saveFileMain(dlg=True) def saveAll(self): for tab in self.iterateTabs(): if tab.fileName and QFileInfo(tab.fileName).isWritable(): tab.saveTextToFile() tab.editBox.document().setModified(False) def saveFileMain(self, dlg): if (not self.currentTab.fileName) or dlg: markupClass = self.currentTab.getMarkupClass() if (markupClass is None) or not hasattr(markupClass, 'default_extension'): defaultExt = self.tr("Plain text (*.txt)") ext = ".txt" else: defaultExt = self.tr('%s files', 'Example of final string: Markdown files') \ % markupClass.name + ' (' + str.join(' ', ('*'+extension for extension in markupClass.file_extensions)) + ')' if markupClass == markups.MarkdownMarkup: ext = globalSettings.markdownDefaultFileExtension elif markupClass == markups.ReStructuredTextMarkup: ext = globalSettings.restDefaultFileExtension else: ext = markupClass.default_extension newFileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", defaultExt)[0] if newFileName: if not QFileInfo(newFileName).suffix(): newFileName += ext if self.currentTab.fileName: self.fileSystemWatcher.removePath(self.currentTab.fileName) self.currentTab.fileName = newFileName self.actionSetEncoding.setDisabled(self.autoSaveActive()) if self.currentTab.fileName: if self.currentTab.saveTextToFile(): self.setCurrentFile() self.currentTab.editBox.document().setModified(False) self.setWindowModified(False) return True else: QMessageBox.warning(self, '', self.tr("Cannot save to file because it is read-only!")) return False def saveHtml(self, fileName): if not QFileInfo(fileName).suffix(): fileName += ".html" try: htmltext = self.currentTab.getHtml(includeStyleSheet=False, webenv=True) except Exception: return self.printError() htmlFile = QFile(fileName) htmlFile.open(QIODevice.WriteOnly) html = QTextStream(htmlFile) if globalSettings.defaultCodec: html.setCodec(globalSettings.defaultCodec) html << htmltext htmlFile.close() def textDocument(self): td = QTextDocument() td.setMetaInformation(QTextDocument.DocumentTitle, self.currentTab.getDocumentTitle()) if self.ss: td.setDefaultStyleSheet(self.ss) td.setHtml(self.currentTab.getHtml()) td.setDefaultFont(globalSettings.font) return td def saveOdf(self): try: document = self.textDocument() except Exception: return self.printError() fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to ODT"), "", self.tr("OpenDocument text files (*.odt)"))[0] if not QFileInfo(fileName).suffix(): fileName += ".odt" writer = QTextDocumentWriter(fileName) writer.setFormat(b"odf") writer.write(document) def saveFileHtml(self): fileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", self.tr("HTML files (*.html *.htm)"))[0] if fileName: self.saveHtml(fileName) def getDocumentForPrint(self): if globalSettings.useWebKit: return self.currentTab.previewBox try: return self.textDocument() except Exception: self.printError() def standardPrinter(self): printer = QPrinter(QPrinter.HighResolution) printer.setDocName(self.currentTab.getDocumentTitle()) printer.setCreator('ReText %s' % app_version) return printer def savePdf(self): self.currentTab.updatePreviewBox() fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to PDF"), "", self.tr("PDF files (*.pdf)"))[0] if fileName: if not QFileInfo(fileName).suffix(): fileName += ".pdf" printer = self.standardPrinter() printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(fileName) document = self.getDocumentForPrint() if document != None: document.print(printer) def printFile(self): self.currentTab.updatePreviewBox() printer = self.standardPrinter() dlg = QPrintDialog(printer, self) dlg.setWindowTitle(self.tr("Print document")) if (dlg.exec() == QDialog.Accepted): document = self.getDocumentForPrint() if document != None: document.print(printer) def printPreview(self): document = self.getDocumentForPrint() if document == None: return printer = self.standardPrinter() preview = QPrintPreviewDialog(printer, self) preview.paintRequested.connect(document.print) preview.exec() def runExtensionCommand(self, command, filefilter, defaultext): of = ('%of' in command) html = ('%html' in command) if of: if defaultext and not filefilter: filefilter = '*'+defaultext fileName = QFileDialog.getSaveFileName(self, self.tr('Export document'), '', filefilter)[0] if not fileName: return if defaultext and not QFileInfo(fileName).suffix(): fileName += defaultext basename = '.%s.retext-temp' % self.currentTab.getDocumentTitle(baseName=True) if html: tmpname = basename+'.html' self.saveHtml(tmpname) else: tmpname = basename + self.currentTab.getMarkupClass().default_extension self.currentTab.saveTextToFile(fileName=tmpname, addToWatcher=False) command = command.replace('%of', '"out'+defaultext+'"') command = command.replace('%html' if html else '%if', '"'+tmpname+'"') try: Popen(str(command), shell=True).wait() except Exception as error: errorstr = str(error) QMessageBox.warning(self, '', self.tr('Failed to execute the command:') + '\n' + errorstr) QFile(tmpname).remove() if of: QFile('out'+defaultext).rename(fileName) def autoSaveActive(self, ind=None): tab = self.currentTab if ind is None else self.tabWidget.widget(ind).tab return (self.autoSaveEnabled and tab.fileName and QFileInfo(tab.fileName).isWritable()) def modificationChanged(self, changed): if self.autoSaveActive(): changed = False self.actionSave.setEnabled(changed) self.setWindowModified(changed) def clipboardDataChanged(self): mimeData = QApplication.instance().clipboard().mimeData() if mimeData is not None: self.actionPaste.setEnabled(mimeData.hasText()) def insertFormatting(self, formatting): cursor = self.currentTab.editBox.textCursor() text = cursor.selectedText() moveCursorTo = None def c(cursor): nonlocal moveCursorTo moveCursorTo = cursor.position() def ensurenl(cursor): if not cursor.atBlockStart(): cursor.insertText('\n\n') toinsert = { 'header': (ensurenl, '# ', text), 'italic': ('*', text, c, '*'), 'bold': ('**', text, c, '**'), 'underline': ('<u>', text, c, '</u>'), 'numbering': (ensurenl, ' 1. ', text), 'bullets': (ensurenl, ' * ', text), 'image': ('![', text or self.tr('Alt text'), c, '](', self.tr('URL'), ')'), 'link': ('[', text or self.tr('Link text'), c, '](', self.tr('URL'), ')'), 'inline code': ('`', text, c, '`'), 'code block': (ensurenl, ' ', text), 'blockquote': (ensurenl, '> ', text), } if formatting not in toinsert: return cursor.beginEditBlock() for token in toinsert[formatting]: if callable(token): token(cursor) else: cursor.insertText(token) cursor.endEditBlock() self.formattingBox.setCurrentIndex(0) # Bring back the focus on the editor self.currentTab.editBox.setFocus(Qt.OtherFocusReason) if moveCursorTo: cursor.setPosition(moveCursorTo) self.currentTab.editBox.setTextCursor(cursor) def insertSymbol(self, num): if num: self.currentTab.editBox.insertPlainText('&'+self.usefulChars[num-1]+';') self.symbolBox.setCurrentIndex(0) def fileChanged(self, fileName): ind = None for testind, tab in enumerate(self.iterateTabs()): if tab.fileName == fileName: ind = testind if ind is None: self.fileSystemWatcher.removePath(fileName) self.tabWidget.setCurrentIndex(ind) if not QFile.exists(fileName): self.currentTab.editBox.document().setModified(True) QMessageBox.warning(self, '', self.tr( 'This file has been deleted by other application.\n' 'Please make sure you save the file before exit.')) elif not self.currentTab.editBox.document().isModified(): # File was not modified in ReText, reload silently self.currentTab.readTextFromFile() self.currentTab.updatePreviewBox() else: text = self.tr( 'This document has been modified by other application.\n' 'Do you want to reload the file (this will discard all ' 'your changes)?\n') if self.autoSaveEnabled: text += self.tr( 'If you choose to not reload the file, auto save mode will ' 'be disabled for this session to prevent data loss.') messageBox = QMessageBox(QMessageBox.Warning, '', text) reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole) messageBox.addButton(QMessageBox.Cancel) messageBox.exec() if messageBox.clickedButton() is reloadButton: self.currentTab.readTextFromFile() self.currentTab.updatePreviewBox() else: self.autoSaveEnabled = False self.currentTab.editBox.document().setModified(True) if fileName not in self.fileSystemWatcher.files(): # https://github.com/retext-project/retext/issues/137 self.fileSystemWatcher.addPath(fileName) def maybeSave(self, ind): tab = self.tabWidget.widget(ind).tab if self.autoSaveActive(ind): tab.saveTextToFile() return True if not tab.editBox.document().isModified(): return True self.tabWidget.setCurrentIndex(ind) ret = QMessageBox.warning(self, '', self.tr("The document has been modified.\nDo you want to save your changes?"), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.saveFileMain(False) elif ret == QMessageBox.Cancel: return False return True def closeEvent(self, closeevent): for ind in range(self.tabWidget.count()): if not self.maybeSave(ind): return closeevent.ignore() if globalSettings.saveWindowGeometry and not self.isMaximized(): globalSettings.windowGeometry = self.saveGeometry() closeevent.accept() def viewHtml(self): htmlDlg = HtmlDialog(self) try: htmltext = self.currentTab.getHtml(includeStyleSheet=False) except Exception: return self.printError() winTitle = self.currentTab.getDocumentTitle(baseName=True) htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")") htmlDlg.textEdit.setPlainText(htmltext.rstrip()) htmlDlg.hl.rehighlight() htmlDlg.show() htmlDlg.raise_() htmlDlg.activateWindow() def openHelp(self): QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki')) def aboutDialog(self): QMessageBox.about(self, self.aboutWindowTitle, '<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__)) +'</b></p>' + self.tr('Simple but powerful editor' ' for Markdown and reStructuredText') +'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011–2016') +'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website') +'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">' +self.tr('Markdown syntax') +'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">' +self.tr('reStructuredText syntax')+'</a></p>') def setDefaultMarkup(self, markupClass): self.defaultMarkup = markupClass defaultName = markups.get_available_markups()[0].name writeToSettings('defaultMarkup', markupClass.name, defaultName) for tab in self.iterateTabs(): if not tab.fileName: tab.setMarkupClass(markupClass) tab.updatePreviewBox() self.docTypeChanged()
def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.centralwidget.hide() self.parser = argparse.ArgumentParser("playground") self.parser.add_argument("-d", "--data-dir", type=str, help="data dir") self.parser.add_argument("-a", "--console-address", type=str, help="console address", default='localhost') self.parser.add_argument("-p", "--console-port", type=int, help="console port", default=2222) self.args = self.parser.parse_args() self.data_dir = self.args.data_dir self.console_port = self.args.console_port self.console_address = self.args.console_address self.project = CetechProject() self.api = QtConsoleAPI(self.console_address, self.console_port) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) self.script_editor_widget = ScriptEditor(project_manager=self.project, api=self.api) self.script_editor_dock_widget = QDockWidget(self) self.script_editor_dock_widget.setWindowTitle("Script editor") self.script_editor_dock_widget.hide() self.script_editor_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.script_editor_dock_widget.setWidget(self.script_editor_widget) self.addDockWidget(Qt.TopDockWidgetArea, self.script_editor_dock_widget) self.log_widget = LogWidget(self.api, self.script_editor_widget) self.log_dock_widget = QDockWidget(self) self.log_dock_widget.hide() self.log_dock_widget.setWindowTitle("Log") self.log_dock_widget.setWidget(self.log_widget) self.addDockWidget(Qt.BottomDockWidgetArea, self.log_dock_widget) self.assetb_widget = AssetBrowser() self.assetb_dock_widget = QDockWidget(self) self.assetb_dock_widget.hide() self.assetb_dock_widget.setWindowTitle("Asset browser") self.assetb_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.assetb_dock_widget.setWidget(self.assetb_widget) self.addDockWidget(Qt.LeftDockWidgetArea, self.assetb_dock_widget) self.recorded_event_widget = RecordEventWidget(api=self.api) self.recorded_event_dock_widget = QDockWidget(self) self.recorded_event_dock_widget.setWindowTitle("Recorded events") self.recorded_event_dock_widget.hide() self.recorded_event_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.recorded_event_dock_widget.setWidget(self.recorded_event_widget) self.addDockWidget(Qt.RightDockWidgetArea, self.recorded_event_dock_widget) #TODO bug #114 workaround. Disable create sub engine... if platform.system().lower() != 'darwin': self.ogl_widget = CetechWidget(self, self.api) self.ogl_dock = QDockWidget(self) self.ogl_dock.hide() self.ogl_dock.setWidget(self.ogl_widget) self.addDockWidget(Qt.TopDockWidgetArea, self.ogl_dock) self.tabifyDockWidget(self.assetb_dock_widget, self.log_dock_widget) self.assetb_widget.asset_clicked.connect(self.open_asset) self.file_watch = QFileSystemWatcher(self) self.file_watch.fileChanged.connect(self.file_changed) self.file_watch.directoryChanged.connect(self.dir_changed) self.build_file_watch = QFileSystemWatcher(self) self.build_file_watch.fileChanged.connect(self.build_file_changed) self.build_file_watch.directoryChanged.connect(self.build_dir_changed)
class _FileWatcher(QObject): """File watcher. QFileSystemWatcher notifies client about any change (file access mode, modification date, etc.) But, we need signal, only after file contents had been changed """ modified = pyqtSignal(bool) removed = pyqtSignal(bool) def __init__(self, path): QObject.__init__(self) self._contents = None self._watcher = QFileSystemWatcher() self._timer = None self._path = path self._lastEmittedModifiedStatus = None self._lastEmittedRemovedStatus = None self.setPath(path) self.enable() def term(self): self.disable() def enable(self): """Enable signals from the watcher """ self._watcher.fileChanged.connect(self._onFileChanged) def disable(self): """Disable signals from the watcher """ self._watcher.fileChanged.disconnect(self._onFileChanged) self._stopTimer() def setContents(self, contents): """Set file contents. Watcher uses it to compare old and new contents of the file. """ self._contents = contents # Qt File watcher may work incorrectly, if file was not existing, when it started if not self._watcher.files(): self.setPath(self._path) self._lastEmittedModifiedStatus = None self._lastEmittedRemovedStatus = None def setPath(self, path): """Path had been changed or file had been created. Set new path """ if self._watcher.files(): self._watcher.removePaths(self._watcher.files()) if path is not None and os.path.isfile(path): self._watcher.addPath(path) self._path = path self._lastEmittedModifiedStatus = None self._lastEmittedRemovedStatus = None def _emitModifiedStatus(self): """Emit self.modified signal with right status """ isModified = self._contents != self._safeRead(self._path) if isModified != self._lastEmittedModifiedStatus: self.modified.emit(isModified) self._lastEmittedModifiedStatus = isModified def _emitRemovedStatus(self, isRemoved): """Emit 'removed', if status changed""" if isRemoved != self._lastEmittedRemovedStatus: self._lastEmittedRemovedStatus = isRemoved self.removed.emit(isRemoved) @pyqtSlot() def _onFileChanged(self): """File changed. Emit own signal, if contents changed """ if os.path.exists(self._path): self._emitModifiedStatus() else: self._emitRemovedStatus(True) # Sometimes QFileSystemWatcher emits only 1 signal for 2 modifications # Check once more later self._startTimer() def _startTimer(self): """Init a timer. It is used for monitoring file after deletion. Git removes file, than restores it. """ if self._timer is None: self._timer = QTimer() self._timer.setInterval(500) self._timer.timeout.connect(self._onCheckIfDeletedTimer) self._timer.start() def _stopTimer(self): """Stop timer, if exists """ if self._timer is not None: self._timer.stop() @pyqtSlot() def _onCheckIfDeletedTimer(self): """Check, if file has been restored """ if os.path.exists(self._path): self.setPath(self._path) # restart Qt file watcher after file has been restored self._stopTimer() self._emitRemovedStatus(False) self._emitModifiedStatus() def _safeRead(self, path): """Read file. Ignore exceptions """ try: with open(path, 'rb') as file: return file.read() except (OSError, IOError): return None
class ExternalEditor(QObject): """Class to simplify editing a text in an external editor. Attributes: _text: The current text before the editor is opened. _filename: The name of the file to be edited. _remove_file: Whether the file should be removed when the editor is closed. _proc: The GUIProcess of the editor. _watcher: A QFileSystemWatcher to watch the edited file for changes. Only set if watch=True. _content: The last-saved text of the editor. Signals: file_updated: The text in the edited file was updated. arg: The new text. editing_finished: The editor process was closed. """ file_updated = pyqtSignal(str) editing_finished = pyqtSignal() def __init__(self, parent=None, watch=False): super().__init__(parent) self._filename = None self._proc = None self._remove_file = None self._watcher = QFileSystemWatcher(parent=self) if watch else None self._content = None def _cleanup(self): """Clean up temporary files after the editor closed.""" assert self._remove_file is not None watched_files = self._watcher.files() if self._watcher else [] if watched_files: failed = self._watcher.removePaths(watched_files) if failed: log.procs.error("Failed to unwatch paths: {}".format(failed)) if self._filename is None or not self._remove_file: # Could not create initial file. return try: if self._proc.exit_status() != QProcess.CrashExit: os.remove(self._filename) except OSError as e: # NOTE: Do not replace this with "raise CommandError" as it's # executed async. message.error("Failed to delete tempfile... ({})".format(e)) @pyqtSlot(int, QProcess.ExitStatus) def _on_proc_closed(self, _exitcode, exitstatus): """Write the editor text into the form field and clean up tempfile. Callback for QProcess when the editor was closed. """ log.procs.debug("Editor closed") if exitstatus != QProcess.NormalExit: # No error/cleanup here, since we already handle this in # on_proc_error. return # do a final read to make sure we don't miss the last signal self._on_file_changed(self._filename) self.editing_finished.emit() self._cleanup() @pyqtSlot(QProcess.ProcessError) def _on_proc_error(self, _err): self._cleanup() def edit(self, text, caret_position=None): """Edit a given text. Args: text: The initial text to edit. caret_position: The position of the caret in the text. """ if self._filename is not None: raise ValueError("Already editing a file!") try: self._filename = self._create_tempfile(text, 'qutebrowser-editor-') except OSError as e: message.error("Failed to create initial file: {}".format(e)) return self._remove_file = True line, column = self._calc_line_and_column(text, caret_position) self._start_editor(line=line, column=column) def backup(self): """Create a backup if the content has changed from the original.""" if not self._content: return try: fname = self._create_tempfile(self._content, 'qutebrowser-editor-backup-') message.info('Editor backup at {}'.format(fname)) except OSError as e: message.error('Failed to create editor backup: {}'.format(e)) def _create_tempfile(self, text, prefix): # Close while the external process is running, as otherwise systems # with exclusive write access (e.g. Windows) may fail to update # the file from the external editor, see # https://github.com/qutebrowser/qutebrowser/issues/1767 with tempfile.NamedTemporaryFile( mode='w', prefix=prefix, encoding=config.val.editor.encoding, delete=False) as fobj: if text: fobj.write(text) return fobj.name @pyqtSlot(str) def _on_file_changed(self, path): try: with open(path, 'r', encoding=config.val.editor.encoding) as f: text = f.read() except OSError as e: # NOTE: Do not replace this with "raise CommandError" as it's # executed async. message.error("Failed to read back edited file: {}".format(e)) return log.procs.debug("Read back: {}".format(text)) if self._content != text: self._content = text self.file_updated.emit(text) def edit_file(self, filename): """Edit the file with the given filename.""" self._filename = filename self._remove_file = False self._start_editor() def _start_editor(self, line=1, column=1): """Start the editor with the file opened as self._filename. Args: line: the line number to pass to the editor column: the column number to pass to the editor """ self._proc = guiprocess.GUIProcess(what='editor', parent=self) self._proc.finished.connect(self._on_proc_closed) self._proc.error.connect(self._on_proc_error) editor = config.val.editor.command executable = editor[0] if self._watcher: ok = self._watcher.addPath(self._filename) if not ok: log.procs.error("Failed to watch path: {}" .format(self._filename)) self._watcher.fileChanged.connect(self._on_file_changed) args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]] log.procs.debug("Calling \"{}\" with args {}".format(executable, args)) self._proc.start(executable, args) def _calc_line_and_column(self, text, caret_position): r"""Calculate line and column numbers given a text and caret position. Both line and column are 1-based indexes, because that's what most editors use as line and column starting index. By "most" we mean at least vim, nvim, gvim, emacs, atom, sublimetext, notepad++, brackets, visual studio, QtCreator and so on. To find the line we just count how many newlines there are before the caret and add 1. To find the column we calculate the difference between the caret and the last newline before the caret. For example in the text `aaa\nbb|bbb` (| represents the caret): caret_position = 6 text[:caret_position] = `aaa\nbb` text[:caret_position].count('\n') = 1 caret_position - text[:caret_position].rfind('\n') = 3 Thus line, column = 2, 3, and the caret is indeed in the second line, third column Args: text: the text for which the numbers must be calculated caret_position: the position of the caret in the text, or None Return: A (line, column) tuple of (int, int) """ if caret_position is None: return 1, 1 line = text[:caret_position].count('\n') + 1 column = caret_position - text[:caret_position].rfind('\n') return line, column def _sub_placeholder(self, arg, line, column): """Substitute a single placeholder. If the `arg` input to this function is a valid placeholder it will be substituted with the appropriate value, otherwise it will be left unchanged. Args: arg: an argument of editor.command. line: the previously-calculated line number for the text caret. column: the previously-calculated column number for the text caret. Return: The substituted placeholder or the original argument. """ replacements = { '{}': self._filename, '{file}': self._filename, '{line}': str(line), '{line0}': str(line-1), '{column}': str(column), '{column0}': str(column-1) } for old, new in replacements.items(): arg = arg.replace(old, new) return arg
class PugdebugDocuments(QObject): watcher = None open_documents = {} document_changed = pyqtSignal(object) def __init__(self): super(PugdebugDocuments, self).__init__() self.watcher = QFileSystemWatcher() self.watcher.fileChanged.connect(self.handle_file_changed) def open_document(self, path): path_key = self.get_path_key(path) document = PugdebugDocument(path) self.open_documents[path_key] = document self.watcher.addPath(path) return document def close_document(self, path): path_key = self.get_path_key(path) self.open_documents.pop(path_key, None) self.watcher.removePath(path) def refresh_document(self, path): """Refresh a document Gets called when the file system watcher notices a change to an open document. """ path_key = self.get_path_key(path) document = self.open_documents[path_key] document.read_file(path) self.document_changed.emit(document) def is_document_open(self, path): path_key = self.get_path_key(path) return path_key in self.open_documents def handle_file_changed(self, path): """Handle when a watched file gets changed Crazy stuff ahead. If a file is modified, some editors (systems?) will first remove the file and then write it back to the disk. And for that split second, the watcher will drop the file from being watched. But then again, maybe that file really got deleted? Who knows?! Anyway, when a file gets modified, we sleep a short while to see if that file will "get back" and if so, add it back to the watcher. If not, we'll assume the file got deleted. """ if not self.__is_path_watched(path): fileinfo = QFileInfo(path) total_slept = 0 file_exists = fileinfo.exists() while not file_exists: sleep_for = 0.1 total_slept += sleep_for if total_slept > 1: break time.sleep(sleep_for) file_exists = fileinfo.exists() if file_exists: self.watcher.addPath(path) self.refresh_document(path) else: # file got deleted? pass def get_path_key(self, path): path_key = hashlib.md5(path.encode("utf-8")) return path_key.hexdigest() def __is_path_watched(self, path): return path in self.watcher.files()
class Scene: def __init__(self) -> None: super().__init__() from UM.Scene.SceneNode import SceneNode self._root = SceneNode(name = "Root") self._root.setCalculateBoundingBox(False) self._connectSignalsRoot() self._active_camera = None # type: Optional[Camera] self._ignore_scene_changes = False self._lock = threading.Lock() # Watching file for changes. self._file_watcher = QFileSystemWatcher() self._file_watcher.fileChanged.connect(self._onFileChanged) self._reload_message = None # type: Optional[Message] def _connectSignalsRoot(self) -> None: self._root.transformationChanged.connect(self.sceneChanged) self._root.childrenChanged.connect(self.sceneChanged) self._root.meshDataChanged.connect(self.sceneChanged) def _disconnectSignalsRoot(self) -> None: self._root.transformationChanged.disconnect(self.sceneChanged) self._root.childrenChanged.disconnect(self.sceneChanged) self._root.meshDataChanged.disconnect(self.sceneChanged) def setIgnoreSceneChanges(self, ignore_scene_changes: bool) -> None: if self._ignore_scene_changes != ignore_scene_changes: self._ignore_scene_changes = ignore_scene_changes if self._ignore_scene_changes: self._disconnectSignalsRoot() else: self._connectSignalsRoot() ## Gets the global scene lock. # # Use this lock to prevent any read or write actions on the scene from other threads, # assuming those threads also properly acquire the lock. Most notably, this # prevents the rendering thread from rendering the scene while it is changing. def getSceneLock(self) -> threading.Lock: return self._lock ## Get the root node of the scene. def getRoot(self) -> "SceneNode": return self._root ## Change the root node of the scene def setRoot(self, node: "SceneNode") -> None: if self._root != node: if not self._ignore_scene_changes: self._disconnectSignalsRoot() self._root = node if not self._ignore_scene_changes: self._connectSignalsRoot() self.rootChanged.emit() rootChanged = Signal() ## Get the camera that should be used for rendering. def getActiveCamera(self) -> Optional[Camera]: return self._active_camera def getAllCameras(self) -> List[Camera]: cameras = [] for node in BreadthFirstIterator(self._root): # type: ignore if isinstance(node, Camera): cameras.append(node) return cameras ## Set the camera that should be used for rendering. # \param name The name of the camera to use. def setActiveCamera(self, name: str) -> None: camera = self.findCamera(name) if camera: self._active_camera = camera else: Logger.log("w", "Couldn't find camera with name [%s] to activate!" % name) ## Signal that is emitted whenever something in the scene changes. # \param object The object that triggered the change. sceneChanged = Signal() ## Find an object by id. # # \param object_id The id of the object to search for, as returned by the python id() method. # # \return The object if found, or None if not. def findObject(self, object_id: int) -> Optional["SceneNode"]: for node in BreadthFirstIterator(self._root): # type: ignore if id(node) == object_id: return node return None def findCamera(self, name: str) -> Optional[Camera]: for node in BreadthFirstIterator(self._root): # type: ignore if isinstance(node, Camera) and node.getName() == name: return node return None ## Add a file to be watched for changes. # \param file_path The path to the file that must be watched. def addWatchedFile(self, file_path: str) -> None: # The QT 5.10.0 issue, only on Windows. Cura crashes after loading a stl file from USB/sd-card/Cloud-based drive if not Platform.isWindows(): self._file_watcher.addPath(file_path) ## Remove a file so that it will no longer be watched for changes. # \param file_path The path to the file that must no longer be watched. def removeWatchedFile(self, file_path: str) -> None: # The QT 5.10.0 issue, only on Windows. Cura crashes after loading a stl file from USB/sd-card/Cloud-based drive if not Platform.isWindows(): self._file_watcher.removePath(file_path) ## Triggered whenever a file is changed that we currently have loaded. def _onFileChanged(self, file_path: str) -> None: if not os.path.isfile(file_path) or os.path.getsize(file_path) == 0: # File doesn't exist any more, or it is empty return # Multiple nodes may be loaded from the same file at different stages. Reload them all. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator # To find which nodes to reload when files have changed. modified_nodes = [node for node in DepthFirstIterator(self.getRoot()) if node.getMeshData() and node.getMeshData().getFileName() == file_path] # type: ignore if modified_nodes: # Hide the message if it was already visible if self._reload_message is not None: self._reload_message.hide() self._reload_message = Message(i18n_catalog.i18nc("@info", "Would you like to reload {filename}?").format(filename = os.path.basename(file_path)), title = i18n_catalog.i18nc("@info:title", "File has been modified")) self._reload_message.addAction("reload", i18n_catalog.i18nc("@action:button", "Reload"), icon = "", description = i18n_catalog.i18nc("@action:description", "This will trigger the modified files to reload from disk.")) self._reload_callback = functools.partial(self._reloadNodes, modified_nodes) self._reload_message.actionTriggered.connect(self._reload_callback) self._reload_message.show() ## Reloads a list of nodes after the user pressed the "Reload" button. # \param nodes The list of nodes that needs to be reloaded. # \param message The message that triggered the action to reload them. # \param action The button that triggered the action to reload them. def _reloadNodes(self, nodes: List["SceneNode"], message: str, action: str) -> None: if action != "reload": return if self._reload_message is not None: self._reload_message.hide() for node in nodes: meshdata = node.getMeshData() if meshdata: filename = meshdata.getFileName() if not filename or not os.path.isfile(filename): # File doesn't exist any more. continue job = ReadMeshJob(filename) self._reload_finished_callback = functools.partial(self._reloadJobFinished, node) job.finished.connect(self._reload_finished_callback) job.start() ## Triggered when reloading has finished. # # This then puts the resulting mesh data in the node. def _reloadJobFinished(self, replaced_node: SceneNode, job: ReadMeshJob) -> None: for node in job.getResult(): mesh_data = node.getMeshData() if mesh_data: replaced_node.setMeshData(mesh_data) else: Logger.log("w", "Could not find a mesh in reloaded node.")
class ImageLabel(QLabel): sizeChanged = pyqtSignal(int, int) def __init__(self, parent=None): super(ImageLabel, self).__init__(parent) self.setBackgroundRole(QPalette.Base) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.setScaledContents(True) self.setAcceptDrops(True) self.setAlignment(Qt.AlignCenter) def dragEnterEvent(self, event): self.setBackgroundRole(QPalette.Highlight) event.acceptProposedAction() def dragMoveEvent(self, event): event.acceptProposedAction() def dropEvent(self, event): # Get path of dropped file mimeData = event.mimeData() if mimeData.hasUrls(): filename = mimeData.urls()[0].toLocalFile() self.load(filename) event.acceptProposedAction() def dragLeaveEvent(self, event): self.clear() event.accept() def load(self, filename): # Load HDR image with FreeImage image = self.loadHDRImage(filename) if image is None: return False # Change window size self.sizeChanged.emit(image.width(), image.height()) # Set the image to imageLabel self.setPixmap(QPixmap.fromImage(image)) self.adjustSize() # Begin to watch modification self.watcher = QFileSystemWatcher() self.watcher.addPath(filename) self.watcher.fileChanged.connect(self.onFileChanged) return True def loadHDRImage(self, filename): try: # Load image img = fi.Image(filename).flipVertical() floats = array.array("f", img.getRaw()) imageArray = np.array(floats).reshape((img.width, img.height, 3)) # HDR compression imageArray_RGB8 = (np.clip(np.power(imageArray, 1/2.2), 0, 1) * 255).astype(np.uint8) # Convert to QImage return QImage(imageArray_RGB8.tostring(), img.width, img.height, QImage.Format_RGB888) except fi.FreeImageError: return None def onFileChanged(self, path): if os.path.isfile(path): self.load(path) else: self.clear() self.sizeChanged.emit(200, 200)
def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.resize(950, 700) screenRect = QDesktopWidget().screenGeometry() if globalSettings.windowGeometry: self.restoreGeometry(globalSettings.windowGeometry) else: self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2) if not screenRect.contains(self.geometry()): self.showMaximized() if globalSettings.iconTheme: QIcon.setThemeName(globalSettings.iconTheme) if QIcon.themeName() in ('hicolor', ''): if not QFile.exists(icon_path + 'document-new.png'): QIcon.setThemeName(get_icon_theme()) if QFile.exists(icon_path+'retext.png'): self.setWindowIcon(QIcon(icon_path+'retext.png')) elif QFile.exists('/usr/share/pixmaps/retext.png'): self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png')) else: self.setWindowIcon(QIcon.fromTheme('retext', QIcon.fromTheme('accessories-text-editor'))) self.tabWidget = QTabWidget(self) self.initTabWidget() self.setCentralWidget(self.tabWidget) self.tabWidget.currentChanged.connect(self.changeIndex) self.tabWidget.tabCloseRequested.connect(self.closeTab) toolBar = QToolBar(self.tr('File toolbar'), self) self.addToolBar(Qt.TopToolBarArea, toolBar) self.editBar = QToolBar(self.tr('Edit toolbar'), self) self.addToolBar(Qt.TopToolBarArea, self.editBar) self.searchBar = QToolBar(self.tr('Search toolbar'), self) self.addToolBar(Qt.BottomToolBarArea, self.searchBar) toolBar.setVisible(not globalSettings.hideToolBar) self.editBar.setVisible(not globalSettings.hideToolBar) self.actionNew = self.act(self.tr('New'), 'document-new', self.createNew, shct=QKeySequence.New) self.actionNew.setPriority(QAction.LowPriority) self.actionOpen = self.act(self.tr('Open'), 'document-open', self.openFile, shct=QKeySequence.Open) self.actionOpen.setPriority(QAction.LowPriority) self.actionSetEncoding = self.act(self.tr('Set encoding'), trig=self.showEncodingDialog) self.actionSetEncoding.setEnabled(False) self.actionReload = self.act(self.tr('Reload'), 'view-refresh', lambda: self.currentTab.readTextFromFile()) self.actionReload.setEnabled(False) self.actionSave = self.act(self.tr('Save'), 'document-save', self.saveFile, shct=QKeySequence.Save) self.actionSave.setEnabled(False) self.actionSave.setPriority(QAction.LowPriority) self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as', self.saveFileAs, shct=QKeySequence.SaveAs) self.actionNextTab = self.act(self.tr('Next tab'), 'go-next', lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown) self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous', lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp) self.actionPrint = self.act(self.tr('Print'), 'document-print', self.printFile, shct=QKeySequence.Print) self.actionPrint.setPriority(QAction.LowPriority) self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview', self.printPreview) self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml) self.actionChangeEditorFont = self.act(self.tr('Change editor font'), trig=self.changeEditorFont) self.actionChangePreviewFont = self.act(self.tr('Change preview font'), trig=self.changePreviewFont) self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find) self.actionSearch.setCheckable(True) self.actionSearch.triggered[bool].connect(self.searchBar.setVisible) self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged) self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E, trigbool=self.preview) if QIcon.hasThemeIcon('document-preview'): self.actionPreview.setIcon(QIcon.fromTheme('document-preview')) elif QIcon.hasThemeIcon('preview-file'): self.actionPreview.setIcon(QIcon.fromTheme('preview-file')) elif QIcon.hasThemeIcon('x-office-document'): self.actionPreview.setIcon(QIcon.fromTheme('x-office-document')) else: self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png')) self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L, trigbool=self.enableLivePreview) menuPreview = QMenu() menuPreview.addAction(self.actionLivePreview) self.actionPreview.setMenu(menuPreview) self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T, trigbool=lambda x: self.currentTab.editBox.enableTableMode(x)) if ReTextFakeVimHandler: self.actionFakeVimMode = self.act(self.tr('FakeVim mode'), shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode) if globalSettings.useFakeVim: self.actionFakeVimMode.setChecked(True) self.enableFakeVimMode(True) self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen', shct=Qt.Key_F11, trigbool=self.enableFullScreen) self.actionFullScreen.setPriority(QAction.LowPriority) self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system', trig=self.openConfigDialog) self.actionConfig.setMenuRole(QAction.PreferencesRole) self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml) self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf) self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf) self.getExportExtensionsList() self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit) self.actionQuit.setMenuRole(QAction.QuitRole) self.actionQuit.triggered.connect(self.close) self.actionUndo = self.act(self.tr('Undo'), 'edit-undo', lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo) self.actionRedo = self.act(self.tr('Redo'), 'edit-redo', lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo) self.actionCopy = self.act(self.tr('Copy'), 'edit-copy', lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy) self.actionCut = self.act(self.tr('Cut'), 'edit-cut', lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut) self.actionPaste = self.act(self.tr('Paste'), 'edit-paste', lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste) self.actionUndo.setEnabled(False) self.actionRedo.setEnabled(False) self.actionCopy.setEnabled(False) self.actionCut.setEnabled(False) qApp = QApplication.instance() qApp.clipboard().dataChanged.connect(self.clipboardDataChanged) self.clipboardDataChanged() if enchant_available: self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck) self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale) self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit) self.actionWebKit.setChecked(globalSettings.useWebKit) self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir) self.actionFind = self.act(self.tr('Next'), 'go-next', self.find, shct=QKeySequence.FindNext) self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous', lambda: self.find(back=True), shct=QKeySequence.FindPrevious) self.actionCloseSearch = self.act(self.tr('Close'), 'window-close', lambda: self.searchBar.setVisible(False)) self.actionCloseSearch.setPriority(QAction.LowPriority) self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp) self.aboutWindowTitle = self.tr('About ReText') self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog) self.actionAbout.setMenuRole(QAction.AboutRole) self.actionAboutQt = self.act(self.tr('About Qt')) self.actionAboutQt.setMenuRole(QAction.AboutQtRole) self.actionAboutQt.triggered.connect(qApp.aboutQt) availableMarkups = markups.get_available_markups() if not availableMarkups: print('Warning: no markups are available!') self.defaultMarkup = availableMarkups[0] if availableMarkups else None if globalSettings.defaultMarkup: mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup) if mc and mc.available(): self.defaultMarkup = mc if len(availableMarkups) > 1: self.chooseGroup = QActionGroup(self) markupActions = [] for markup in availableMarkups: markupAction = self.act(markup.name, trigbool=self.markupFunction(markup)) if markup == self.defaultMarkup: markupAction.setChecked(True) self.chooseGroup.addAction(markupAction) markupActions.append(markupAction) self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold, trig=lambda: self.insertFormatting('bold')) self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic, trig=lambda: self.insertFormatting('italic')) self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline, trig=lambda: self.insertFormatting('underline')) self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering', 'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote') self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr', 'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo', 'rarr', 'rsquo', 'times') self.formattingBox = QComboBox(self.editBar) self.formattingBox.addItem(self.tr('Formatting')) self.formattingBox.addItems(self.usefulTags) self.formattingBox.activated[str].connect(self.insertFormatting) self.symbolBox = QComboBox(self.editBar) self.symbolBox.addItem(self.tr('Symbols')) self.symbolBox.addItems(self.usefulChars) self.symbolBox.activated.connect(self.insertSymbol) self.updateStyleSheet() menubar = self.menuBar() menuFile = menubar.addMenu(self.tr('File')) menuEdit = menubar.addMenu(self.tr('Edit')) menuHelp = menubar.addMenu(self.tr('Help')) menuFile.addAction(self.actionNew) menuFile.addAction(self.actionOpen) self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent')) self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles) menuFile.addAction(self.actionShow) menuFile.addAction(self.actionSetEncoding) menuFile.addAction(self.actionReload) menuFile.addSeparator() menuFile.addAction(self.actionSave) menuFile.addAction(self.actionSaveAs) menuFile.addSeparator() menuFile.addAction(self.actionNextTab) menuFile.addAction(self.actionPrevTab) menuFile.addSeparator() menuExport = menuFile.addMenu(self.tr('Export')) menuExport.addAction(self.actionSaveHtml) menuExport.addAction(self.actionOdf) menuExport.addAction(self.actionPdf) if self.extensionActions: menuExport.addSeparator() for action, mimetype in self.extensionActions: menuExport.addAction(action) menuExport.aboutToShow.connect(self.updateExtensionsVisibility) menuFile.addAction(self.actionPrint) menuFile.addAction(self.actionPrintPreview) menuFile.addSeparator() menuFile.addAction(self.actionQuit) menuEdit.addAction(self.actionUndo) menuEdit.addAction(self.actionRedo) menuEdit.addSeparator() menuEdit.addAction(self.actionCut) menuEdit.addAction(self.actionCopy) menuEdit.addAction(self.actionPaste) menuEdit.addSeparator() if enchant_available: menuSC = menuEdit.addMenu(self.tr('Spell check')) menuSC.addAction(self.actionEnableSC) menuSC.addAction(self.actionSetLocale) menuEdit.addAction(self.actionSearch) menuEdit.addAction(self.actionChangeEditorFont) menuEdit.addAction(self.actionChangePreviewFont) menuEdit.addSeparator() if len(availableMarkups) > 1: self.menuMode = menuEdit.addMenu(self.tr('Default markup')) for markupAction in markupActions: self.menuMode.addAction(markupAction) menuFormat = menuEdit.addMenu(self.tr('Formatting')) menuFormat.addAction(self.actionBold) menuFormat.addAction(self.actionItalic) menuFormat.addAction(self.actionUnderline) menuEdit.addAction(self.actionWebKit) menuEdit.addSeparator() menuEdit.addAction(self.actionViewHtml) menuEdit.addAction(self.actionPreview) menuEdit.addAction(self.actionTableMode) if ReTextFakeVimHandler: menuEdit.addAction(self.actionFakeVimMode) menuEdit.addSeparator() menuEdit.addAction(self.actionFullScreen) menuEdit.addAction(self.actionConfig) menuHelp.addAction(self.actionHelp) menuHelp.addSeparator() menuHelp.addAction(self.actionAbout) menuHelp.addAction(self.actionAboutQt) toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toolBar.addAction(self.actionNew) toolBar.addSeparator() toolBar.addAction(self.actionOpen) toolBar.addAction(self.actionSave) toolBar.addAction(self.actionPrint) toolBar.addSeparator() toolBar.addAction(self.actionPreview) toolBar.addAction(self.actionFullScreen) self.editBar.addAction(self.actionUndo) self.editBar.addAction(self.actionRedo) self.editBar.addSeparator() self.editBar.addAction(self.actionCut) self.editBar.addAction(self.actionCopy) self.editBar.addAction(self.actionPaste) self.editBar.addSeparator() self.editBar.addWidget(self.formattingBox) self.editBar.addWidget(self.symbolBox) self.searchEdit = QLineEdit(self.searchBar) self.searchEdit.setPlaceholderText(self.tr('Search')) self.searchEdit.returnPressed.connect(self.find) self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar) self.searchBar.addWidget(self.searchEdit) self.searchBar.addSeparator() self.searchBar.addWidget(self.csBox) self.searchBar.addAction(self.actionFindPrev) self.searchBar.addAction(self.actionFind) self.searchBar.addAction(self.actionCloseSearch) self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.searchBar.setVisible(False) self.autoSaveEnabled = globalSettings.autoSave if self.autoSaveEnabled: timer = QTimer(self) timer.start(60000) timer.timeout.connect(self.saveAll) self.ind = None if enchant_available: self.sl = globalSettings.spellCheckLocale if self.sl: try: enchant.Dict(self.sl) except Exception as e: print(e, file=sys.stderr) self.sl = None if globalSettings.spellCheck: self.actionEnableSC.setChecked(True) self.fileSystemWatcher = QFileSystemWatcher() self.fileSystemWatcher.fileChanged.connect(self.fileChanged)
class NFile(QObject): """ SIGNALS: @askForSaveFileClosing(QString) @fileClosing(QString) @fileChanged() @willDelete(PyQt_PyObject, PyQt_PyObject) @willOverWrite(PyQt_PyObject, QString, QString) @willMove(Qt_PyQtObject, QString, QString) @willSave(QString, QString) @savedAsNewFile(PyQt_PyObject, QString, QString) @gotAPath(PyQt_PyObject) @willAttachToExistingFile(PyQt_PyObject, QString) """ fileChanged = pyqtSignal() fileRemoved = pyqtSignal() fileReaded = pyqtSignal() willAttachToExistingFile = pyqtSignal('PyQt_PyObject', 'QString') gotAPath = pyqtSignal('PyQt_PyObject') willSave = pyqtSignal('QString', 'QString') willMove = pyqtSignal('PyQt_PyObject', 'QString', 'QString') willOverWrite = pyqtSignal('PyQt_PyObject', 'QString', 'QString') willCopyTo = pyqtSignal('PyQt_PyObject', 'QString', 'QString') willDelete = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject') fileClosing = pyqtSignal('QString', bool) def __init__(self, path=None): """ """ self._file_path = path self.__created = False self.__watcher = None self.__mtime = None super(NFile, self).__init__() if not self._exists(): self.__created = True @property def file_name(self): """"Returns filename of nfile""" file_name = None if self._file_path is None: file_name = translations.TR_NEW_DOCUMENT else: file_name = get_basename(self._file_path) return file_name @property def display_name(self): """Returns a pretty name to be displayed by tabs""" display_name = self.file_name if self._file_path is not None and not self.has_write_permission(): display_name += translations.TR_READ_ONLY return display_name @property def is_new_file(self): return self.__created def file_ext(self): """"Returns extension of nfile""" if self._file_path is None: return '' return get_file_extension(self._file_path) @property def file_path(self): """"Returns file path of nfile""" return self._file_path def start_watching(self): """Create a file system watcher and connect its fileChanged SIGNAL to our _file_changed SLOT""" if self.__watcher is None: self.__watcher = QFileSystemWatcher(self) self.__watcher.fileChanged['const QString&'].connect( self._file_changed) if self._file_path is not None: self.__mtime = os.path.getmtime(self._file_path) self.__watcher.addPath(self._file_path) def _file_changed(self, path): if self._exists(): current_mtime = os.path.getmtime(self._file_path) if current_mtime != self.__mtime: self.__mtime = current_mtime self.fileChanged.emit() # FIXME: for swap file # else: # self.fileRemoved.emit() def has_write_permission(self): if not self._exists(): return True return os.access(self._file_path, os.W_OK) def _exists(self): """ Check if we have been created with a path and if such path exists In case there is no path, we are most likely a new file. """ file_exists = False if self._file_path and os.path.exists(self._file_path): file_exists = True return file_exists def attach_to_path(self, new_path): if os.path.exists(new_path): signal_handler = SignalFlowControl() self.willAttachToExistingFile.emit(signal_handler, new_path) if signal_handler.stopped(): return self._file_path = new_path self.gotAPath.emit(self) return self._file_path def create(self): if self.__created: self.save("") self.__created = False def save(self, content, path=None): """ Write a temporary file with .tnj extension and copy it over the original one. .nsf = Ninja Swap File # FIXME: Where to locate addExtension, does not fit here """ new_path = False if path: self.attach_to_path(path) new_path = True save_path = self._file_path if not save_path: raise NinjaNoFileNameException("I am asked to write a " "file but no one told me where") swap_save_path = "%s.nsp" % save_path # If we have a file system watcher, remove the file path # from its watch list until we are done making changes. if self.__watcher is not None: self.__watcher.removePath(save_path) flags = QIODevice.WriteOnly | QIODevice.Truncate f = QFile(swap_save_path) if settings.use_platform_specific_eol(): flags |= QIODevice.Text if not f.open(flags): raise NinjaIOException(f.errorString()) stream = QTextStream(f) encoding = get_file_encoding(content) if encoding: stream.setCodec(encoding) encoded_stream = stream.codec().fromUnicode(content) f.write(encoded_stream) f.flush() f.close() # SIGNAL: Will save (temp, definitive) to warn folder to do something self.willSave.emit(swap_save_path, save_path) self.__mtime = os.path.getmtime(swap_save_path) shutil.move(swap_save_path, save_path) self.reset_state() # If we have a file system watcher, add the saved path back # to its watch list, otherwise create a watcher and start # watching if self.__watcher is not None: if new_path: # self.__watcher.removePath(self.__watcher.files()[0]) self.__watcher.addPath(self._file_path) else: self.__watcher.addPath(save_path) else: self.start_watching() return self def reset_state(self): """ #FIXE: to have a ref to changed I need to have the doc here """ self.__created = False def read(self, path=None): """ Read the file or fail """ open_path = path and path or self._file_path self._file_path = open_path if not self._file_path: raise NinjaNoFileNameException("I am asked to read a file " "but no one told me from where") try: with open(open_path, 'r') as f: content = f.read() except (IOError, UnicodeDecodeError) as reason: raise NinjaIOException(reason) self.fileReaded.emit() return content def move(self, new_path): """ Phisically move the file """ if self._exists(): signal_handler = SignalFlowControl() # SIGNALL: WILL MOVE TO, to warn folder to exist self.willMove.emit(signal_handler, self._file_path, new_path) if signal_handler.stopped(): return if os.path.exists(new_path): signal_handler = SignalFlowControl() self.willOverWrite.emit(signal_handler, self._file_path, new_path) if signal_handler.stopped(): return if self.__watcher is not None: self.__watcher.removePath(self._file_path) shutil.move(self._file_path, new_path) if self.__watcher: self.__watcher.addPath(new_path) self._file_path = new_path def copy(self, new_path): """ Copy the file to a new path """ if self._exists(): signal_handler = SignalFlowControl() # SIGNALL: WILL COPY TO, to warn folder to exist self.willCopyTo.emit(signal_handler, self._file_path, new_path) if signal_handler.stopped(): return if os.path.exists(new_path): signal_handler = SignalFlowControl() self.willOverWrite.emit(signal_handler, self._file_path, new_path) if signal_handler.stopped(): return shutil.copy(self._file_path, new_path) def delete(self, force=False): """ This deletes the object and closes the file. """ # if created but exists this file migth to someone else self.close() if ((not self.__created) or force) and self._exists(): DEBUG("Deleting our own NFile %s" % self._file_path) signal_handler = SignalFlowControl() self.willDelete.emit(signal_handler, self) if not signal_handler.stopped(): if self.__watcher is not None: self.__watcher.removePath(self._file_path) os.remove(self._file_path) def close(self, force_close=False): """ Lets let people know we are going down so they can act upon As you can see close does nothing but let everyone know that we are not saved yet """ DEBUG("About to close NFile") self.fileClosing.emit(self._file_path, force_close) def remove_watcher(self): if self.__watcher is not None: self.__watcher.removePath(self._file_path)
def main(): # Command-line arguments parser = argparse.ArgumentParser( prog='qbar', description="An easily-configurable and good-looking status bar for Linux") parser.add_argument( "--monitor", "-m", type=str, default=None, help="Name of the monitor to display the bar on") # TODO: use css for this instead? parser.add_argument( "--height", type=int, default=30, help="Height (in px) of the status bar") parser.add_argument( "--css", type=str, default=None, help="Stylesheet file to override the default style") parser.add_argument( "--config", "-c", default=None, help="Congifuration file for qbar written in yaml. Looks under $XDG_HOME or .config for qbar/config.yml by default. if not found, uses a builtin default") parser.add_argument( "--position", "-p", type=str, default="top", choices=["top", "bottom"], help="Display position of the bar. Can be 'top' or 'bottom'") parser.add_argument( "--log-level", "-l", default='WARNING', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], help="Change the level of information to be logged.") ARGS = parser.parse_args() # Log level for the root logger numeric_log_level = getattr(logging, ARGS.log_level.upper(), None) logging.getLogger().setLevel(numeric_log_level) # Init app app = QApplication(sys.argv) bar = Bar(ARGS.monitor, height=ARGS.height, position=ARGS.position) # Set default stylesheet and override with custom one if present stylesheet_files = [os.path.abspath(os.path.dirname(__file__)) + "/../configs/default.css"] if ARGS.css != None: stylesheet_files.append(ARGS.css) css_loader = StyleSheetLoader(stylesheet_files, lambda styles: app.setStyleSheet(styles)) # Find config file path cfgfile = ARGS.config if ARGS.config != None else default_user_config_dir() + '/config.py' if not os.path.exists(cfgfile): if ARGS.config != None: logging.error("Specified config file does not exist: %s" % ARGS.config) logging.info("Using builtin default config file") cfgfile = os.path.abspath(os.path.dirname(__file__)) + "/../configs/default.py" # Load config file and set it up to reload whenever the file changes cfgmodule = load_config_module(cfgfile) bar.items = cfgmodule.items def reload_config(filepath): logging.info("Config file changed, reloading: %s" % filepath) try: importlib.reload(cfgmodule) bar.items = cfgmodule.items except SyntaxError as e: logging.error("SyntaxError encountered when attempting to load config file: %s" % str(e)) bar.items = [] watcher = QFileSystemWatcher() watcher.addPath(cfgfile) watcher.fileChanged.connect(reload_config) # Run! bar.start() bar.show() # Exit app on ctrl+c. # Note that a timer must be present in order for Ctrl+C to work. Otherwise # the python interpreter never gets a chance to run and handle the signal. sigtimer = QTimer() sigtimer.timeout.connect(lambda: None) sigtimer.start(500) def sigint_handler(signal, frame): print("\nReceived Ctrl+C, exiting...") bar.stop() QApplication.quit() signal.signal(signal.SIGINT, sigint_handler) ret = app.exec_() sys.exit(ret)
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.centralwidget.hide() self.project = ProjectManager() self.modules_manager = ModuleManager(self) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) self.modules_manager.init_modules([ CompilerModule, LevelWidget, AssetViewWidget, AssetBrowser, LogWidget, ProfilerWidget, ScriptEditorManager ]) self.file_watch = QFileSystemWatcher(self) self.file_watch.fileChanged.connect(self.file_changed) self.file_watch.directoryChanged.connect(self.dir_changed) self.build_file_watch = QFileSystemWatcher(self) self.build_file_watch.fileChanged.connect(self.build_file_changed) self.build_file_watch.directoryChanged.connect(self.build_dir_changed) def open_project(self, name, dir): self.project.open_project(name, dir) self.modules_manager.open_project(self.project) self.watch_project_dir() def reload_all(self): self.modules_manager['compiler'].compile_all() for k, v in self.project.instances.items(): v.console_api.reload_all() def watch_project_dir(self): files = self.file_watch.files() directories = self.file_watch.directories() if len(files): self.file_watch.removePaths(files) if len(directories): self.file_watch.removePaths(directories) files = self.build_file_watch.files() directories = self.build_file_watch.directories() if len(files): self.build_file_watch.removePaths(files) if len(directories): self.build_file_watch.removePaths(directories) files = [] it = QDirIterator(self.project.source_dir, QDirIterator.Subdirectories) while it.hasNext(): files.append(it.next()) self.file_watch.addPaths(files) files = [] it = QDirIterator(self.project.build_dir, QDirIterator.Subdirectories) while it.hasNext(): files.append(it.next()) self.build_file_watch.addPaths(files) def file_changed(self, path): self.modules_manager['compiler'].compile_all() def dir_changed(self, path): self.watch_project_dir() def build_file_changed(self, path): pass def build_dir_changed(self, path): pass def open_script_editor(self): self.script_editor_dock_widget.show() def open_recorded_events(self): self.modules_manager['profiler'].dock.show() def run_standalone(self): self.project.run_release("Standalone") def run_level(self): self.project.run_develop("Level", compile_=True, continue_=True, port=5566) def closeEvent(self, evnt): self.modules_manager.close_project() self.project.killall() evnt.accept()
class ReTextWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.resize(950, 700) screenRect = QDesktopWidget().screenGeometry() if globalSettings.windowGeometry: self.restoreGeometry(globalSettings.windowGeometry) else: self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2) if not screenRect.contains(self.geometry()): self.showMaximized() if globalSettings.iconTheme: QIcon.setThemeName(globalSettings.iconTheme) if QIcon.themeName() in ('hicolor', ''): if not QFile.exists(icon_path + 'document-new.png'): QIcon.setThemeName(get_icon_theme()) if QFile.exists(icon_path+'retext.png'): self.setWindowIcon(QIcon(icon_path+'retext.png')) elif QFile.exists('/usr/share/pixmaps/retext.png'): self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png')) else: self.setWindowIcon(QIcon.fromTheme('retext', QIcon.fromTheme('accessories-text-editor'))) self.editBoxes = [] self.previewBoxes = [] self.highlighters = [] self.markups = [] self.fileNames = [] self.actionPreviewChecked = [] self.actionLivePreviewChecked = [] self.tabWidget = QTabWidget(self) self.initTabWidget() self.setCentralWidget(self.tabWidget) self.tabWidget.currentChanged.connect(self.changeIndex) self.tabWidget.tabCloseRequested.connect(self.closeTab) toolBar = QToolBar(self.tr('File toolbar'), self) self.addToolBar(Qt.TopToolBarArea, toolBar) self.editBar = QToolBar(self.tr('Edit toolbar'), self) self.addToolBar(Qt.TopToolBarArea, self.editBar) self.searchBar = QToolBar(self.tr('Search toolbar'), self) self.addToolBar(Qt.BottomToolBarArea, self.searchBar) toolBar.setVisible(not globalSettings.hideToolBar) self.editBar.setVisible(not globalSettings.hideToolBar) self.actionNew = self.act(self.tr('New'), 'document-new', self.createNew, shct=QKeySequence.New) self.actionNew.setPriority(QAction.LowPriority) self.actionOpen = self.act(self.tr('Open'), 'document-open', self.openFile, shct=QKeySequence.Open) self.actionOpen.setPriority(QAction.LowPriority) self.actionSetEncoding = self.act(self.tr('Set encoding'), trig=self.showEncodingDialog) self.actionSetEncoding.setEnabled(False) self.actionReload = self.act(self.tr('Reload'), 'view-refresh', trig=self.openFileMain) self.actionReload.setEnabled(False) self.actionSave = self.act(self.tr('Save'), 'document-save', self.saveFile, shct=QKeySequence.Save) self.actionSave.setEnabled(False) self.actionSave.setPriority(QAction.LowPriority) self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as', self.saveFileAs, shct=QKeySequence.SaveAs) self.actionNextTab = self.act(self.tr('Next tab'), 'go-next', lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown) self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous', lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp) self.actionPrint = self.act(self.tr('Print'), 'document-print', self.printFile, shct=QKeySequence.Print) self.actionPrint.setPriority(QAction.LowPriority) self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview', self.printPreview) self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml) self.actionChangeEditorFont = self.act(self.tr('Change editor font'), trig=self.changeEditorFont) self.actionChangePreviewFont = self.act(self.tr('Change preview font'), trig=self.changePreviewFont) self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find) self.actionSearch.setCheckable(True) self.actionSearch.triggered[bool].connect(self.searchBar.setVisible) self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged) self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E, trigbool=self.preview) if QIcon.hasThemeIcon('document-preview'): self.actionPreview.setIcon(QIcon.fromTheme('document-preview')) elif QIcon.hasThemeIcon('preview-file'): self.actionPreview.setIcon(QIcon.fromTheme('preview-file')) elif QIcon.hasThemeIcon('x-office-document'): self.actionPreview.setIcon(QIcon.fromTheme('x-office-document')) else: self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png')) self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L, trigbool=self.enableLivePreview) self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T, trigbool=lambda x: self.editBoxes[self.ind].enableTableMode(x)) if ReTextFakeVimHandler: self.actionFakeVimMode = self.act(self.tr('FakeVim mode'), shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode) if globalSettings.useFakeVim: self.actionFakeVimMode.setChecked(True) self.enableFakeVimMode(True) self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen', shct=Qt.Key_F11, trigbool=self.enableFullScreen) self.actionFullScreen.setPriority(QAction.LowPriority) self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system', trig=self.openConfigDialog) self.actionConfig.setMenuRole(QAction.PreferencesRole) self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml) self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf) self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf) self.getExportExtensionsList() self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit) self.actionQuit.setMenuRole(QAction.QuitRole) self.actionQuit.triggered.connect(self.close) self.actionUndo = self.act(self.tr('Undo'), 'edit-undo', lambda: self.editBoxes[self.ind].undo(), shct=QKeySequence.Undo) self.actionRedo = self.act(self.tr('Redo'), 'edit-redo', lambda: self.editBoxes[self.ind].redo(), shct=QKeySequence.Redo) self.actionCopy = self.act(self.tr('Copy'), 'edit-copy', lambda: self.editBoxes[self.ind].copy(), shct=QKeySequence.Copy) self.actionCut = self.act(self.tr('Cut'), 'edit-cut', lambda: self.editBoxes[self.ind].cut(), shct=QKeySequence.Cut) self.actionPaste = self.act(self.tr('Paste'), 'edit-paste', lambda: self.editBoxes[self.ind].paste(), shct=QKeySequence.Paste) self.actionUndo.setEnabled(False) self.actionRedo.setEnabled(False) self.actionCopy.setEnabled(False) self.actionCut.setEnabled(False) qApp = QApplication.instance() qApp.clipboard().dataChanged.connect(self.clipboardDataChanged) self.clipboardDataChanged() if enchant_available: self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck) self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale) self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit) self.actionWebKit.setChecked(globalSettings.useWebKit) self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir) self.actionFind = self.act(self.tr('Next'), 'go-next', self.find, shct=QKeySequence.FindNext) self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous', lambda: self.find(back=True), shct=QKeySequence.FindPrevious) self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp) self.aboutWindowTitle = self.tr('About ReText') self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog) self.actionAbout.setMenuRole(QAction.AboutRole) self.actionAboutQt = self.act(self.tr('About Qt')) self.actionAboutQt.setMenuRole(QAction.AboutQtRole) self.actionAboutQt.triggered.connect(qApp.aboutQt) availableMarkups = markups.get_available_markups() if not availableMarkups: print('Warning: no markups are available!') self.defaultMarkup = availableMarkups[0] if availableMarkups else None if globalSettings.defaultMarkup: mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup) if mc and mc.available(): self.defaultMarkup = mc if len(availableMarkups) > 1: self.chooseGroup = QActionGroup(self) markupActions = [] for markup in availableMarkups: markupAction = self.act(markup.name, trigbool=self.markupFunction(markup)) if markup == self.defaultMarkup: markupAction.setChecked(True) self.chooseGroup.addAction(markupAction) markupActions.append(markupAction) self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold, trig=lambda: self.insertChars('**')) self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic, trig=lambda: self.insertChars('*')) self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline, trig=lambda: self.insertTag('u')) self.usefulTags = ('a', 'big', 'center', 'img', 's', 'small', 'span', 'table', 'td', 'tr', 'u') self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr', 'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo', 'rarr', 'rsquo', 'times') self.tagsBox = QComboBox(self.editBar) self.tagsBox.addItem(self.tr('Tags')) self.tagsBox.addItems(self.usefulTags) self.tagsBox.activated.connect(self.insertTag) self.symbolBox = QComboBox(self.editBar) self.symbolBox.addItem(self.tr('Symbols')) self.symbolBox.addItems(self.usefulChars) self.symbolBox.activated.connect(self.insertSymbol) self.updateStyleSheet() menubar = QMenuBar(self) menubar.setGeometry(QRect(0, 0, 800, 25)) self.setMenuBar(menubar) menuFile = menubar.addMenu(self.tr('File')) menuEdit = menubar.addMenu(self.tr('Edit')) menuHelp = menubar.addMenu(self.tr('Help')) menuFile.addAction(self.actionNew) menuFile.addAction(self.actionOpen) self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent')) self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles) menuFile.addMenu(self.menuRecentFiles) menuFile.addAction(self.actionShow) menuFile.addAction(self.actionSetEncoding) menuFile.addAction(self.actionReload) menuFile.addSeparator() menuFile.addAction(self.actionSave) menuFile.addAction(self.actionSaveAs) menuFile.addSeparator() menuFile.addAction(self.actionNextTab) menuFile.addAction(self.actionPrevTab) menuFile.addSeparator() menuExport = menuFile.addMenu(self.tr('Export')) menuExport.addAction(self.actionSaveHtml) menuExport.addAction(self.actionOdf) menuExport.addAction(self.actionPdf) if self.extensionActions: menuExport.addSeparator() for action, mimetype in self.extensionActions: menuExport.addAction(action) menuExport.aboutToShow.connect(self.updateExtensionsVisibility) menuFile.addAction(self.actionPrint) menuFile.addAction(self.actionPrintPreview) menuFile.addSeparator() menuFile.addAction(self.actionQuit) menuEdit.addAction(self.actionUndo) menuEdit.addAction(self.actionRedo) menuEdit.addSeparator() menuEdit.addAction(self.actionCut) menuEdit.addAction(self.actionCopy) menuEdit.addAction(self.actionPaste) menuEdit.addSeparator() if enchant_available: menuSC = menuEdit.addMenu(self.tr('Spell check')) menuSC.addAction(self.actionEnableSC) menuSC.addAction(self.actionSetLocale) menuEdit.addAction(self.actionSearch) menuEdit.addAction(self.actionChangeEditorFont) menuEdit.addAction(self.actionChangePreviewFont) menuEdit.addSeparator() if len(availableMarkups) > 1: self.menuMode = menuEdit.addMenu(self.tr('Default markup')) for markupAction in markupActions: self.menuMode.addAction(markupAction) menuFormat = menuEdit.addMenu(self.tr('Formatting')) menuFormat.addAction(self.actionBold) menuFormat.addAction(self.actionItalic) menuFormat.addAction(self.actionUnderline) menuEdit.addAction(self.actionWebKit) menuEdit.addSeparator() menuEdit.addAction(self.actionViewHtml) menuEdit.addAction(self.actionLivePreview) menuEdit.addAction(self.actionPreview) menuEdit.addAction(self.actionTableMode) if ReTextFakeVimHandler: menuEdit.addAction(self.actionFakeVimMode) menuEdit.addSeparator() menuEdit.addAction(self.actionFullScreen) menuEdit.addAction(self.actionConfig) menuHelp.addAction(self.actionHelp) menuHelp.addSeparator() menuHelp.addAction(self.actionAbout) menuHelp.addAction(self.actionAboutQt) menubar.addMenu(menuFile) menubar.addMenu(menuEdit) menubar.addMenu(menuHelp) toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toolBar.addAction(self.actionNew) toolBar.addSeparator() toolBar.addAction(self.actionOpen) toolBar.addAction(self.actionSave) toolBar.addAction(self.actionPrint) toolBar.addSeparator() toolBar.addAction(self.actionPreview) toolBar.addAction(self.actionFullScreen) self.editBar.addAction(self.actionUndo) self.editBar.addAction(self.actionRedo) self.editBar.addSeparator() self.editBar.addAction(self.actionCut) self.editBar.addAction(self.actionCopy) self.editBar.addAction(self.actionPaste) self.editBar.addSeparator() self.editBar.addWidget(self.tagsBox) self.editBar.addWidget(self.symbolBox) self.searchEdit = QLineEdit(self.searchBar) self.searchEdit.setPlaceholderText(self.tr('Search')) self.searchEdit.returnPressed.connect(self.find) self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar) self.searchBar.addWidget(self.searchEdit) self.searchBar.addSeparator() self.searchBar.addWidget(self.csBox) self.searchBar.addAction(self.actionFindPrev) self.searchBar.addAction(self.actionFind) self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.searchBar.setVisible(False) self.autoSaveEnabled = globalSettings.autoSave if self.autoSaveEnabled: timer = QTimer(self) timer.start(60000) timer.timeout.connect(self.saveAll) self.ind = None if enchant_available: self.sl = globalSettings.spellCheckLocale if self.sl: try: enchant.Dict(self.sl) except Exception as e: print(e, file=sys.stderr) self.sl = None if globalSettings.spellCheck: self.actionEnableSC.setChecked(True) self.enableSpellCheck(True) self.fileSystemWatcher = QFileSystemWatcher() self.fileSystemWatcher.fileChanged.connect(self.fileChanged) def updateStyleSheet(self): if globalSettings.styleSheet: sheetfile = QFile(globalSettings.styleSheet) sheetfile.open(QIODevice.ReadOnly) self.ss = QTextStream(sheetfile).readAll() sheetfile.close() else: self.ss = '' def initTabWidget(self): def dragEnterEvent(e): e.acceptProposedAction() def dropEvent(e): fn = bytes(e.mimeData().data('text/plain')).decode().rstrip() if fn.startswith('file:'): fn = QUrl(fn).toLocalFile() self.openFileWrapper(fn) self.tabWidget.setTabsClosable(True) self.tabWidget.setAcceptDrops(True) self.tabWidget.dragEnterEvent = dragEnterEvent self.tabWidget.dropEvent = dropEvent def act(self, name, icon=None, trig=None, trigbool=None, shct=None): if not isinstance(shct, QKeySequence): shct = QKeySequence(shct) if icon: action = QAction(self.actIcon(icon), name, self) else: action = QAction(name, self) if trig: action.triggered.connect(trig) elif trigbool: action.setCheckable(True) action.triggered[bool].connect(trigbool) if shct: action.setShortcut(shct) return action def actIcon(self, name): return QIcon.fromTheme(name, QIcon(icon_path+name+'.png')) def printError(self): import traceback print('Exception occured while parsing document:', file=sys.stderr) traceback.print_exc() def getSplitter(self, index): splitter = QSplitter(Qt.Horizontal) # Give both boxes a minimum size so the minimumSizeHint will be # ignored when splitter.setSizes is called below for widget in self.editBoxes[index], self.previewBoxes[index]: widget.setMinimumWidth(125) splitter.addWidget(widget) splitter.setSizes((50, 50)) splitter.setChildrenCollapsible(False) return splitter def getWebView(self): webView = QWebView() if not globalSettings.handleWebLinks: webView.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks) webView.page().linkClicked.connect(QDesktopServices.openUrl) settings = webView.settings() settings.setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False) settings.setDefaultTextEncoding('utf-8') return webView def createTab(self, fileName): self.previewBlocked = False self.editBoxes.append(ReTextEdit(self)) self.highlighters.append(ReTextHighlighter(self.editBoxes[-1].document())) if enchant_available and self.actionEnableSC.isChecked(): self.highlighters[-1].dictionary = \ enchant.Dict(self.sl) if self.sl else enchant.Dict() self.highlighters[-1].rehighlight() if globalSettings.useWebKit: self.previewBoxes.append(self.getWebView()) else: self.previewBoxes.append(QTextBrowser()) self.previewBoxes[-1].setOpenExternalLinks(True) self.previewBoxes[-1].setVisible(False) self.fileNames.append(fileName) markupClass = self.getMarkupClass(fileName) self.markups.append(self.getMarkup(fileName)) self.highlighters[-1].docType = (markupClass.name if markupClass else '') liveMode = globalSettings.restorePreviewState and globalSettings.previewState self.actionPreviewChecked.append(liveMode) self.actionLivePreviewChecked.append(liveMode) metrics = QFontMetrics(self.editBoxes[-1].font()) self.editBoxes[-1].setTabStopWidth(globalSettings.tabWidth * metrics.width(' ')) self.editBoxes[-1].textChanged.connect(self.updateLivePreviewBox) self.editBoxes[-1].undoAvailable.connect(self.actionUndo.setEnabled) self.editBoxes[-1].redoAvailable.connect(self.actionRedo.setEnabled) self.editBoxes[-1].copyAvailable.connect(self.enableCopy) self.editBoxes[-1].document().modificationChanged.connect(self.modificationChanged) if globalSettings.useFakeVim: self.installFakeVimHandler(self.editBoxes[-1]) return self.getSplitter(-1) def closeTab(self, ind): if self.maybeSave(ind): if self.tabWidget.count() == 1: self.tabWidget.addTab(self.createTab(""), self.tr("New document")) if self.fileNames[ind]: self.fileSystemWatcher.removePath(self.fileNames[ind]) del self.editBoxes[ind] del self.previewBoxes[ind] del self.highlighters[ind] del self.markups[ind] del self.fileNames[ind] del self.actionPreviewChecked[ind] del self.actionLivePreviewChecked[ind] self.tabWidget.removeTab(ind) def getMarkupClass(self, fileName=None): if fileName is None: fileName = self.fileNames[self.ind] if fileName: markupClass = markups.get_markup_for_file_name( fileName, return_class=True) if markupClass: return markupClass return self.defaultMarkup def getMarkup(self, fileName=None): if fileName is None: fileName = self.fileNames[self.ind] markupClass = self.getMarkupClass(fileName=fileName) if markupClass and markupClass.available(): return markupClass(filename=fileName) def docTypeChanged(self): oldType = self.highlighters[self.ind].docType markupClass = self.getMarkupClass() newType = markupClass.name if markupClass else '' if oldType != newType: self.markups[self.ind] = self.getMarkup() self.updatePreviewBox() self.highlighters[self.ind].docType = newType self.highlighters[self.ind].rehighlight() dtMarkdown = (newType == DOCTYPE_MARKDOWN) dtMkdOrReST = (newType in (DOCTYPE_MARKDOWN, DOCTYPE_REST)) self.tagsBox.setEnabled(dtMarkdown) self.symbolBox.setEnabled(dtMarkdown) self.actionUnderline.setEnabled(dtMarkdown) self.actionBold.setEnabled(dtMkdOrReST) self.actionItalic.setEnabled(dtMkdOrReST) canReload = bool(self.fileNames[self.ind]) and not self.autoSaveActive() self.actionSetEncoding.setEnabled(canReload) self.actionReload.setEnabled(canReload) def changeIndex(self, ind): if ind > -1: self.actionUndo.setEnabled(self.editBoxes[ind].document().isUndoAvailable()) self.actionRedo.setEnabled(self.editBoxes[ind].document().isRedoAvailable()) self.actionCopy.setEnabled(self.editBoxes[ind].textCursor().hasSelection()) self.actionCut.setEnabled(self.editBoxes[ind].textCursor().hasSelection()) self.actionPreview.setChecked(self.actionPreviewChecked[ind]) self.actionLivePreview.setChecked(self.actionLivePreviewChecked[ind]) self.actionTableMode.setChecked(self.editBoxes[ind].tableModeEnabled) self.editBar.setDisabled(self.actionPreviewChecked[ind]) self.ind = ind if self.fileNames[ind]: self.setCurrentFile() else: self.setWindowTitle(self.tr('New document') + '[*]') self.docTypeChanged() self.modificationChanged(self.editBoxes[ind].document().isModified()) if globalSettings.restorePreviewState: globalSettings.previewState = self.actionLivePreviewChecked[ind] if self.actionLivePreviewChecked[ind]: self.enableLivePreview(True) self.editBoxes[self.ind].setFocus(Qt.OtherFocusReason) def changeEditorFont(self): font, ok = QFontDialog.getFont(globalSettings.editorFont, self) if ok: globalSettings.editorFont = font for editor in self.editBoxes: editor.updateFont() def changePreviewFont(self): font, ok = QFontDialog.getFont(globalSettings.font, self) if ok: globalSettings.font = font self.updatePreviewBox() def preview(self, viewmode): self.actionPreviewChecked[self.ind] = viewmode if self.actionLivePreview.isChecked(): self.actionLivePreview.setChecked(False) return self.enableLivePreview(False) self.editBar.setDisabled(viewmode) self.editBoxes[self.ind].setVisible(not viewmode) self.previewBoxes[self.ind].setVisible(viewmode) if viewmode: self.updatePreviewBox() def enableLivePreview(self, livemode): if globalSettings.restorePreviewState: globalSettings.previewState = livemode self.actionLivePreviewChecked[self.ind] = livemode self.actionPreviewChecked[self.ind] = livemode self.actionPreview.setChecked(livemode) self.editBar.setEnabled(True) self.previewBoxes[self.ind].setVisible(livemode) self.editBoxes[self.ind].setVisible(True) if livemode: self.updatePreviewBox() def enableWebKit(self, enable): globalSettings.useWebKit = enable oldind = self.ind self.tabWidget.clear() for self.ind in range(len(self.editBoxes)): if enable: self.previewBoxes[self.ind] = self.getWebView() else: self.previewBoxes[self.ind] = QTextBrowser() self.previewBoxes[self.ind].setOpenExternalLinks(True) splitter = self.getSplitter(self.ind) self.tabWidget.addTab(splitter, self.getDocumentTitle(baseName=True)) self.updatePreviewBox() self.previewBoxes[self.ind].setVisible(self.actionPreviewChecked[self.ind]) self.ind = oldind self.tabWidget.setCurrentIndex(self.ind) def enableCopy(self, copymode): self.actionCopy.setEnabled(copymode) self.actionCut.setEnabled(copymode) def enableFullScreen(self, yes): if yes: self.showFullScreen() else: self.showNormal() def openConfigDialog(self): dlg = ConfigDialog(self) dlg.setWindowTitle(self.tr('Preferences')) dlg.show() def installFakeVimHandler(self, editor): if ReTextFakeVimHandler: fakeVimEditor = ReTextFakeVimHandler(editor, self) fakeVimEditor.setSaveAction(self.actionSave) fakeVimEditor.setQuitAction(self.actionQuit) self.actionFakeVimMode.triggered.connect(fakeVimEditor.remove) def enableFakeVimMode(self, yes): globalSettings.useFakeVim = yes if yes: FakeVimMode.init(self) for editor in self.editBoxes: self.installFakeVimHandler(editor) else: FakeVimMode.exit(self) def enableSpellCheck(self, yes): if yes: if self.sl: self.setAllDictionaries(enchant.Dict(self.sl)) else: self.setAllDictionaries(enchant.Dict()) else: self.setAllDictionaries(None) globalSettings.spellCheck = yes def setAllDictionaries(self, dictionary): for hl in self.highlighters: hl.dictionary = dictionary hl.rehighlight() def changeLocale(self): if self.sl: localedlg = LocaleDialog(self, defaultText=self.sl) else: localedlg = LocaleDialog(self) if localedlg.exec() != QDialog.Accepted: return sl = localedlg.localeEdit.text() setdefault = localedlg.checkBox.isChecked() if sl: try: sl = str(sl) enchant.Dict(sl) except Exception as e: QMessageBox.warning(self, '', str(e)) else: self.sl = sl self.enableSpellCheck(self.actionEnableSC.isChecked()) else: self.sl = None self.enableSpellCheck(self.actionEnableSC.isChecked()) if setdefault: globalSettings.spellCheckLocale = sl def searchBarVisibilityChanged(self, visible): self.actionSearch.setChecked(visible) if visible: self.searchEdit.setFocus(Qt.ShortcutFocusReason) def find(self, back=False): flags = QTextDocument.FindFlags() if back: flags |= QTextDocument.FindBackward if self.csBox.isChecked(): flags |= QTextDocument.FindCaseSensitively text = self.searchEdit.text() editBox = self.editBoxes[self.ind] cursor = editBox.textCursor() newCursor = editBox.document().find(text, cursor, flags) if not newCursor.isNull(): editBox.setTextCursor(newCursor) return self.setSearchEditColor(True) cursor.movePosition(QTextCursor.End if back else QTextCursor.Start) newCursor = editBox.document().find(text, cursor, flags) if not newCursor.isNull(): editBox.setTextCursor(newCursor) return self.setSearchEditColor(True) self.setSearchEditColor(False) def setSearchEditColor(self, found): palette = self.searchEdit.palette() palette.setColor(QPalette.Active, QPalette.Base, Qt.white if found else QColor(255, 102, 102)) self.searchEdit.setPalette(palette) def getHtml(self, includeStyleSheet=True, includeTitle=True, includeMeta=False, webenv=False): if self.markups[self.ind] is None: markupClass = self.getMarkupClass() errMsg = self.tr('Could not parse file contents, check if ' 'you have the <a href="%s">necessary module</a> installed!') try: errMsg %= markupClass.attributes[MODULE_HOME_PAGE] except (AttributeError, KeyError): # Remove the link if markupClass doesn't have the needed attribute errMsg = errMsg.replace('<a href="%s">', '') errMsg = errMsg.replace('</a>', '') return '<p style="color: red">%s</p>' % errMsg text = self.editBoxes[self.ind].toPlainText() headers = '' if includeStyleSheet: headers += '<style type="text/css">\n' + self.ss + '</style>\n' cssFileName = self.getDocumentTitle(baseName=True)+'.css' if QFile(cssFileName).exists(): headers += '<link rel="stylesheet" type="text/css" href="%s">\n' \ % cssFileName if includeMeta: headers += ('<meta name="generator" content="ReText %s">\n' % app_version) fallbackTitle = self.getDocumentTitle() if includeTitle else '' return self.markups[self.ind].get_whole_html(text, custom_headers=headers, include_stylesheet=includeStyleSheet, fallback_title=fallbackTitle, webenv=webenv) def updatePreviewBox(self): self.previewBlocked = False pb = self.previewBoxes[self.ind] textedit = isinstance(pb, QTextEdit) if textedit: scrollbar = pb.verticalScrollBar() disttobottom = scrollbar.maximum() - scrollbar.value() else: frame = pb.page().mainFrame() scrollpos = frame.scrollPosition() try: html = self.getHtml() except Exception: return self.printError() if textedit: pb.setHtml(html) pb.document().setDefaultFont(globalSettings.font) scrollbar.setValue(scrollbar.maximum() - disttobottom) else: pb.settings().setFontFamily(QWebSettings.StandardFont, globalSettings.font.family()) pb.settings().setFontSize(QWebSettings.DefaultFontSize, globalSettings.font.pointSize()) pb.setHtml(html, QUrl.fromLocalFile(self.fileNames[self.ind])) frame.setScrollPosition(scrollpos) def updateLivePreviewBox(self): if self.actionLivePreview.isChecked() and self.previewBlocked == False: self.previewBlocked = True QTimer.singleShot(1000, self.updatePreviewBox) def showInDir(self): if self.fileNames[self.ind]: path = QFileInfo(self.fileNames[self.ind]).path() QDesktopServices.openUrl(QUrl.fromLocalFile(path)) else: QMessageBox.warning(self, '', self.tr("Please, save the file somewhere.")) def setCurrentFile(self): self.setWindowTitle("") self.tabWidget.setTabText(self.ind, self.getDocumentTitle(baseName=True)) self.setWindowFilePath(self.fileNames[self.ind]) files = readListFromSettings("recentFileList") while self.fileNames[self.ind] in files: files.remove(self.fileNames[self.ind]) files.insert(0, self.fileNames[self.ind]) if len(files) > 10: del files[10:] writeListToSettings("recentFileList", files) QDir.setCurrent(QFileInfo(self.fileNames[self.ind]).dir().path()) self.docTypeChanged() def createNew(self, text=None): self.tabWidget.addTab(self.createTab(""), self.tr("New document")) self.ind = self.tabWidget.count()-1 self.tabWidget.setCurrentIndex(self.ind) if text: self.editBoxes[self.ind].textCursor().insertText(text) def switchTab(self, shift=1): self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count()) def updateRecentFiles(self): self.menuRecentFiles.clear() self.recentFilesActions = [] filesOld = readListFromSettings("recentFileList") files = [] for f in filesOld: if QFile.exists(f): files.append(f) self.recentFilesActions.append(self.act(f, trig=self.openFunction(f))) writeListToSettings("recentFileList", files) for action in self.recentFilesActions: self.menuRecentFiles.addAction(action) def markupFunction(self, markup): return lambda: self.setDefaultMarkup(markup) def openFunction(self, fileName): return lambda: self.openFileWrapper(fileName) def extensionFuntion(self, data): return lambda: \ self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension']) def getExportExtensionsList(self): extensions = [] for extsprefix in datadirs: extsdir = QDir(extsprefix+'/export-extensions/') if extsdir.exists(): for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'], QDir.Files | QDir.Readable): extensions.append(self.readExtension(fileInfo.filePath())) locale = QLocale.system().name() self.extensionActions = [] for extension in extensions: try: if ('Name[%s]' % locale) in extension: name = extension['Name[%s]' % locale] elif ('Name[%s]' % locale.split('_')[0]) in extension: name = extension['Name[%s]' % locale.split('_')[0]] else: name = extension['Name'] data = {} for prop in ('FileFilter', 'DefaultExtension', 'Exec'): if 'X-ReText-'+prop in extension: data[prop] = extension['X-ReText-'+prop] elif prop in extension: data[prop] = extension[prop] else: data[prop] = '' action = self.act(name, trig=self.extensionFuntion(data)) if 'Icon' in extension: action.setIcon(self.actIcon(extension['Icon'])) mimetype = extension['MimeType'] if 'MimeType' in extension else None except KeyError: print('Failed to parse extension: Name is required', file=sys.stderr) else: self.extensionActions.append((action, mimetype)) def updateExtensionsVisibility(self): markupClass = self.getMarkupClass() for action in self.extensionActions: if markupClass is None: action[0].setEnabled(False) continue mimetype = action[1] if mimetype == None: enabled = True elif markupClass == markups.MarkdownMarkup: enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown")) elif markupClass == markups.ReStructuredTextMarkup: enabled = (mimetype in ("text/x-retext-rst", "text/x-rst")) else: enabled = False action[0].setEnabled(enabled) def readExtension(self, fileName): extFile = QFile(fileName) extFile.open(QIODevice.ReadOnly) extension = {} stream = QTextStream(extFile) while not stream.atEnd(): line = stream.readLine() if '=' in line: index = line.index('=') extension[line[:index].rstrip()] = line[index+1:].lstrip() extFile.close() return extension def openFile(self): supportedExtensions = ['.txt'] for markup in markups.get_all_markups(): supportedExtensions += markup.file_extensions fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;' fileNames = QFileDialog.getOpenFileNames(self, self.tr("Select one or several files to open"), "", self.tr("Supported files") + fileFilter + self.tr("All files (*)")) for fileName in fileNames[0]: self.openFileWrapper(fileName) def openFileWrapper(self, fileName): if not fileName: return fileName = QFileInfo(fileName).canonicalFilePath() exists = False for i in range(self.tabWidget.count()): if self.fileNames[i] == fileName: exists = True ex = i if exists: self.tabWidget.setCurrentIndex(ex) elif QFile.exists(fileName): noEmptyTab = ( (self.ind is None) or self.fileNames[self.ind] or self.editBoxes[self.ind].toPlainText() or self.editBoxes[self.ind].document().isModified() ) if noEmptyTab: self.tabWidget.addTab(self.createTab(fileName), "") self.ind = self.tabWidget.count()-1 self.tabWidget.setCurrentIndex(self.ind) if fileName: self.fileSystemWatcher.addPath(fileName) self.fileNames[self.ind] = fileName self.openFileMain() def openFileMain(self, encoding=None): openfile = QFile(self.fileNames[self.ind]) openfile.open(QIODevice.ReadOnly) stream = QTextStream(openfile) if encoding: stream.setCodec(encoding) elif globalSettings.defaultCodec: stream.setCodec(globalSettings.defaultCodec) text = stream.readAll() openfile.close() markupClass = markups.get_markup_for_file_name( self.fileNames[self.ind], return_class=True) self.highlighters[self.ind].docType = (markupClass.name if markupClass else '') self.markups[self.ind] = self.getMarkup() if self.defaultMarkup: self.highlighters[self.ind].docType = self.defaultMarkup.name editBox = self.editBoxes[self.ind] modified = bool(encoding) and (editBox.toPlainText() != text) editBox.setPlainText(text) self.setCurrentFile() editBox.document().setModified(modified) self.setWindowModified(modified) def showEncodingDialog(self): if not self.maybeSave(self.ind): return encoding, ok = QInputDialog.getItem(self, '', self.tr('Select file encoding from the list:'), [bytes(b).decode() for b in QTextCodec.availableCodecs()], 0, False) if ok: self.openFileMain(encoding) def saveFile(self): self.saveFileMain(dlg=False) def saveFileAs(self): self.saveFileMain(dlg=True) def saveAll(self): oldind = self.ind for self.ind in range(self.tabWidget.count()): if self.fileNames[self.ind] and QFileInfo(self.fileNames[self.ind]).isWritable(): self.saveFileCore(self.fileNames[self.ind]) self.editBoxes[self.ind].document().setModified(False) self.ind = oldind def saveFileMain(self, dlg): if (not self.fileNames[self.ind]) or dlg: markupClass = self.getMarkupClass() if (markupClass is None) or not hasattr(markupClass, 'default_extension'): defaultExt = self.tr("Plain text (*.txt)") ext = ".txt" else: defaultExt = self.tr('%s files', 'Example of final string: Markdown files') \ % markupClass.name + ' (' + str.join(' ', ('*'+extension for extension in markupClass.file_extensions)) + ')' if markupClass == markups.MarkdownMarkup: ext = globalSettings.markdownDefaultFileExtension elif markupClass == markups.ReStructuredTextMarkup: ext = globalSettings.restDefaultFileExtension else: ext = markupClass.default_extension newFileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", defaultExt)[0] if newFileName: if not QFileInfo(newFileName).suffix(): newFileName += ext if self.fileNames[self.ind]: self.fileSystemWatcher.removePath(self.fileNames[self.ind]) self.fileNames[self.ind] = newFileName self.actionSetEncoding.setDisabled(self.autoSaveActive()) if self.fileNames[self.ind]: result = self.saveFileCore(self.fileNames[self.ind]) if result: self.setCurrentFile() self.editBoxes[self.ind].document().setModified(False) self.setWindowModified(False) return True else: QMessageBox.warning(self, '', self.tr("Cannot save to file because it is read-only!")) return False def saveFileCore(self, fn, addToWatcher=True): self.fileSystemWatcher.removePath(fn) savefile = QFile(fn) result = savefile.open(QIODevice.WriteOnly) if result: savestream = QTextStream(savefile) if globalSettings.defaultCodec: savestream.setCodec(globalSettings.defaultCodec) savestream << self.editBoxes[self.ind].toPlainText() savefile.close() if result and addToWatcher: self.fileSystemWatcher.addPath(fn) return result def saveHtml(self, fileName): if not QFileInfo(fileName).suffix(): fileName += ".html" try: htmltext = self.getHtml(includeStyleSheet=False, includeMeta=True, webenv=True) except Exception: return self.printError() htmlFile = QFile(fileName) htmlFile.open(QIODevice.WriteOnly) html = QTextStream(htmlFile) if globalSettings.defaultCodec: html.setCodec(globalSettings.defaultCodec) html << htmltext htmlFile.close() def textDocument(self): td = QTextDocument() td.setMetaInformation(QTextDocument.DocumentTitle, self.getDocumentTitle()) if self.ss: td.setDefaultStyleSheet(self.ss) td.setHtml(self.getHtml()) td.setDefaultFont(globalSettings.font) return td def saveOdf(self): try: document = self.textDocument() except Exception: return self.printError() fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to ODT"), "", self.tr("OpenDocument text files (*.odt)"))[0] if not QFileInfo(fileName).suffix(): fileName += ".odt" writer = QTextDocumentWriter(fileName) writer.setFormat("odf") writer.write(document) def saveFileHtml(self): fileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", self.tr("HTML files (*.html *.htm)"))[0] if fileName: self.saveHtml(fileName) def getDocumentForPrint(self): if globalSettings.useWebKit: return self.previewBoxes[self.ind] try: return self.textDocument() except Exception: self.printError() def standardPrinter(self): printer = QPrinter(QPrinter.HighResolution) printer.setDocName(self.getDocumentTitle()) printer.setCreator('ReText %s' % app_version) return printer def savePdf(self): self.updatePreviewBox() fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to PDF"), "", self.tr("PDF files (*.pdf)"))[0] if fileName: if not QFileInfo(fileName).suffix(): fileName += ".pdf" printer = self.standardPrinter() printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(fileName) document = self.getDocumentForPrint() if document != None: document.print(printer) def printFile(self): self.updatePreviewBox() printer = self.standardPrinter() dlg = QPrintDialog(printer, self) dlg.setWindowTitle(self.tr("Print document")) if (dlg.exec() == QDialog.Accepted): document = self.getDocumentForPrint() if document != None: document.print(printer) def printPreview(self): document = self.getDocumentForPrint() if document == None: return printer = self.standardPrinter() preview = QPrintPreviewDialog(printer, self) preview.paintRequested.connect(document.print) preview.exec() def runExtensionCommand(self, command, filefilter, defaultext): of = ('%of' in command) html = ('%html' in command) if of: if defaultext and not filefilter: filefilter = '*'+defaultext fileName = QFileDialog.getSaveFileName(self, self.tr('Export document'), '', filefilter)[0] if not fileName: return if defaultext and not QFileInfo(fileName).suffix(): fileName += defaultext basename = '.%s.retext-temp' % self.getDocumentTitle(baseName=True) if html: tmpname = basename+'.html' self.saveHtml(tmpname) else: tmpname = basename+self.getMarkupClass().default_extension self.saveFileCore(tmpname, addToWatcher=False) command = command.replace('%of', '"out'+defaultext+'"') command = command.replace('%html' if html else '%if', '"'+tmpname+'"') try: Popen(str(command), shell=True).wait() except Exception as error: errorstr = str(error) QMessageBox.warning(self, '', self.tr('Failed to execute the command:') + '\n' + errorstr) QFile(tmpname).remove() if of: QFile('out'+defaultext).rename(fileName) def getDocumentTitle(self, baseName=False): markup = self.markups[self.ind] realTitle = '' if markup and not baseName: text = self.editBoxes[self.ind].toPlainText() try: realTitle = markup.get_document_title(text) except Exception: self.printError() if realTitle: return realTitle elif self.fileNames[self.ind]: fileinfo = QFileInfo(self.fileNames[self.ind]) basename = fileinfo.completeBaseName() return (basename if basename else fileinfo.fileName()) return self.tr("New document") def autoSaveActive(self): return self.autoSaveEnabled and self.fileNames[self.ind] and \ QFileInfo(self.fileNames[self.ind]).isWritable() def modificationChanged(self, changed): if self.autoSaveActive(): changed = False self.actionSave.setEnabled(changed) self.setWindowModified(changed) def clipboardDataChanged(self): mimeData = QApplication.instance().clipboard().mimeData() if mimeData is not None: self.actionPaste.setEnabled(mimeData.hasText()) def insertChars(self, chars): tc = self.editBoxes[self.ind].textCursor() if tc.hasSelection(): selection = tc.selectedText() if selection.startswith(chars) and selection.endswith(chars): if len(selection) > 2*len(chars): selection = selection[len(chars):-len(chars)] tc.insertText(selection) else: tc.insertText(chars+tc.selectedText()+chars) else: tc.insertText(chars) def insertTag(self, ut): if not ut: return if isinstance(ut, int): ut = self.usefulTags[ut - 1] arg = ' style=""' if ut == 'span' else '' tc = self.editBoxes[self.ind].textCursor() if ut == 'img': toinsert = ('<a href="' + tc.selectedText() + '" target="_blank"><img src="' + tc.selectedText() + '"/></a>') elif ut == 'a': toinsert = ('<a href="' + tc.selectedText() + '" target="_blank">' + tc.selectedText() + '</a>') else: toinsert = '<'+ut+arg+'>'+tc.selectedText()+'</'+ut+'>' tc.insertText(toinsert) self.tagsBox.setCurrentIndex(0) def insertSymbol(self, num): if num: self.editBoxes[self.ind].insertPlainText('&'+self.usefulChars[num-1]+';') self.symbolBox.setCurrentIndex(0) def fileChanged(self, fileName): ind = self.fileNames.index(fileName) self.tabWidget.setCurrentIndex(ind) if not QFile.exists(fileName): self.editBoxes[ind].document().setModified(True) QMessageBox.warning(self, '', self.tr( 'This file has been deleted by other application.\n' 'Please make sure you save the file before exit.')) elif not self.editBoxes[ind].document().isModified(): # File was not modified in ReText, reload silently self.openFileMain() self.updatePreviewBox() else: text = self.tr( 'This document has been modified by other application.\n' 'Do you want to reload the file (this will discard all ' 'your changes)?\n') if self.autoSaveEnabled: text += self.tr( 'If you choose to not reload the file, auto save mode will ' 'be disabled for this session to prevent data loss.') messageBox = QMessageBox(QMessageBox.Warning, '', text) reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole) messageBox.addButton(QMessageBox.Cancel) messageBox.exec() if messageBox.clickedButton() is reloadButton: self.openFileMain() self.updatePreviewBox() else: self.autoSaveEnabled = False self.editBoxes[ind].document().setModified(True) if fileName not in self.fileSystemWatcher.files(): # https://github.com/retext-project/retext/issues/137 self.fileSystemWatcher.addPath(fileName) def maybeSave(self, ind): if self.autoSaveActive(): self.saveFileCore(self.fileNames[self.ind]) return True if not self.editBoxes[ind].document().isModified(): return True self.tabWidget.setCurrentIndex(ind) ret = QMessageBox.warning(self, '', self.tr("The document has been modified.\nDo you want to save your changes?"), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.saveFileMain(False) elif ret == QMessageBox.Cancel: return False return True def closeEvent(self, closeevent): for self.ind in range(self.tabWidget.count()): if not self.maybeSave(self.ind): return closeevent.ignore() if globalSettings.saveWindowGeometry and not self.isMaximized(): globalSettings.windowGeometry = self.saveGeometry() closeevent.accept() def viewHtml(self): htmlDlg = HtmlDialog(self) try: htmltext = self.getHtml(includeStyleSheet=False, includeTitle=False) except Exception: return self.printError() winTitle = self.getDocumentTitle(baseName=True) htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")") htmlDlg.textEdit.setPlainText(htmltext.rstrip()) htmlDlg.hl.rehighlight() htmlDlg.show() htmlDlg.raise_() htmlDlg.activateWindow() def openHelp(self): QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki')) def aboutDialog(self): QMessageBox.about(self, self.aboutWindowTitle, '<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__)) +'</b></p>' + self.tr('Simple but powerful editor' ' for Markdown and reStructuredText') +'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011\u2013' '2015') +'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website') +'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">' +self.tr('Markdown syntax') +'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">' +self.tr('reStructuredText syntax')+'</a></p>') def setDefaultMarkup(self, markup): self.defaultMarkup = markup defaultName = markups.get_available_markups()[0].name writeToSettings('defaultMarkup', markup.name, defaultName) oldind = self.ind for self.ind in range(len(self.previewBoxes)): self.docTypeChanged() self.ind = oldind
class ProjectBrowserModel(BrowserModel): """ Class implementing the project browser model. @signal vcsStateChanged(str) emitted after the VCS state has changed """ vcsStateChanged = pyqtSignal(str) def __init__(self, parent): """ Constructor @param parent reference to parent object (Project.Project) """ super(ProjectBrowserModel, self).__init__(parent, nopopulate=True) rootData = self.tr("Name") self.rootItem = BrowserItem(None, rootData) self.rootItem.itemData.append(self.tr("VCS Status")) self.progDir = None self.project = parent self.watchedItems = {} self.watcher = QFileSystemWatcher(self) self.watcher.directoryChanged.connect(self.directoryChanged) self.inRefresh = False self.projectBrowserTypes = { "SOURCES": ProjectBrowserSourceType, "FORMS": ProjectBrowserFormType, "RESOURCES": ProjectBrowserResourceType, "INTERFACES": ProjectBrowserInterfaceType, "TRANSLATIONS": ProjectBrowserTranslationType, "OTHERS": ProjectBrowserOthersType, } self.colorNames = { "A": "VcsAdded", "M": "VcsModified", "O": "VcsRemoved", "R": "VcsReplaced", "U": "VcsUpdate", "Z": "VcsConflict", } self.itemBackgroundColors = { " ": QColor(), "A": Preferences.getProjectBrowserColour(self.colorNames["A"]), "M": Preferences.getProjectBrowserColour(self.colorNames["M"]), "O": Preferences.getProjectBrowserColour(self.colorNames["O"]), "R": Preferences.getProjectBrowserColour(self.colorNames["R"]), "U": Preferences.getProjectBrowserColour(self.colorNames["U"]), "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]), } self.highLightColor = \ Preferences.getProjectBrowserColour("Highlighted") # needed by preferencesChanged() self.vcsStatusReport = {} def data(self, index, role): """ Public method to get data of an item. @param index index of the data to retrieve (QModelIndex) @param role role of data (Qt.ItemDataRole) @return requested data """ if not index.isValid(): return None if role == Qt.TextColorRole: if index.column() == 0: try: return index.internalPointer().getTextColor() except AttributeError: return None elif role == Qt.BackgroundColorRole: try: col = self.itemBackgroundColors[ index.internalPointer().vcsState] if col.isValid(): return col else: return None except AttributeError: return None except KeyError: return None return BrowserModel.data(self, index, role) def populateItem(self, parentItem, repopulate=False): """ Public method to populate an item's subtree. @param parentItem reference to the item to be populated @param repopulate flag indicating a repopulation (boolean) """ if parentItem.type() == ProjectBrowserItemSimpleDirectory: return # nothing to do elif parentItem.type() == ProjectBrowserItemDirectory: self.populateProjectDirectoryItem(parentItem, repopulate) elif parentItem.type() == ProjectBrowserItemFile: self.populateFileItem(parentItem, repopulate) else: BrowserModel.populateItem(self, parentItem, repopulate) def populateProjectDirectoryItem(self, parentItem, repopulate=False): """ Public method to populate a directory item's subtree. @param parentItem reference to the directory item to be populated @param repopulate flag indicating a repopulation (boolean) """ self._addWatchedItem(parentItem) qdir = QDir(parentItem.dirName()) if Preferences.getUI("BrowsersListHiddenFiles"): filter = QDir.Filters(QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot) else: filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot) entryInfoList = qdir.entryInfoList(filter) if len(entryInfoList) > 0: if repopulate: self.beginInsertRows(self.createIndex( parentItem.row(), 0, parentItem), 0, len(entryInfoList) - 1) states = {} if self.project.vcs is not None: for f in entryInfoList: fname = f.absoluteFilePath() states[os.path.normcase(fname)] = 0 dname = parentItem.dirName() self.project.vcs.clearStatusCache() states = self.project.vcs.vcsAllRegisteredStates(states, dname) for f in entryInfoList: if f.isDir(): node = ProjectBrowserDirectoryItem( parentItem, Utilities.toNativeSeparators(f.absoluteFilePath()), parentItem.getProjectTypes()[0], False) else: node = ProjectBrowserFileItem( parentItem, Utilities.toNativeSeparators(f.absoluteFilePath()), parentItem.getProjectTypes()[0]) if self.project.vcs is not None: fname = f.absoluteFilePath() if states[os.path.normcase(fname)] == \ self.project.vcs.canBeCommitted: node.addVcsStatus(self.project.vcs.vcsName()) self.project.clearStatusMonitorCachedState( f.absoluteFilePath()) else: node.addVcsStatus(self.tr("local")) self._addItem(node, parentItem) if repopulate: self.endInsertRows() def projectClosed(self): """ Public method called after a project has been closed. """ self.__vcsStatus = {} self.watchedItems = {} watchedDirs = self.watcher.directories() if watchedDirs: self.watcher.removePaths(watchedDirs) self.rootItem.removeChildren() self.beginResetModel() self.endResetModel() # reset the module parser cache Utilities.ModuleParser.resetParsedModules() def projectOpened(self): """ Public method used to populate the model after a project has been opened. """ self.__vcsStatus = {} states = {} keys = list(self.projectBrowserTypes.keys())[:] if self.project.vcs is not None: for key in keys: for fn in self.project.pdata[key]: states[os.path.normcase( os.path.join(self.project.ppath, fn))] = 0 self.project.vcs.clearStatusCache() states = self.project.vcs.vcsAllRegisteredStates( states, self.project.ppath) self.inRefresh = True for key in keys: # Show the entry in bold in the others browser to make it more # distinguishable if key == "OTHERS": bold = True else: bold = False if key == "SOURCES": sourceLanguage = self.project.pdata["PROGLANGUAGE"][0] else: sourceLanguage = "" for fn in self.project.pdata[key]: fname = os.path.join(self.project.ppath, fn) parentItem, dt = self.findParentItemByName( self.projectBrowserTypes[key], fn) if os.path.isdir(fname): itm = ProjectBrowserDirectoryItem( parentItem, fname, self.projectBrowserTypes[key], False, bold) else: itm = ProjectBrowserFileItem( parentItem, fname, self.projectBrowserTypes[key], False, bold, sourceLanguage=sourceLanguage) self._addItem(itm, parentItem) if self.project.vcs is not None: if states[os.path.normcase(fname)] == \ self.project.vcs.canBeCommitted: itm.addVcsStatus(self.project.vcs.vcsName()) else: itm.addVcsStatus(self.tr("local")) else: itm.addVcsStatus("") self.inRefresh = False self.beginResetModel() self.endResetModel() def findParentItemByName(self, type_, name, dontSplit=False): """ Public method to find an item given its name. <b>Note</b>: This method creates all necessary parent items, if they don't exist. @param type_ type of the item @param name name of the item (string) @param dontSplit flag indicating the name should not be split (boolean) @return reference to the item found and the new display name (string) """ if dontSplit: pathlist = [] pathlist.append(name) pathlist.append("ignore_me") else: pathlist = re.split(r'/|\\', name) if len(pathlist) > 1: olditem = self.rootItem path = self.project.ppath for p in pathlist[:-1]: itm = self.findChildItem(p, 0, olditem) path = os.path.join(path, p) if itm is None: itm = ProjectBrowserSimpleDirectoryItem( olditem, type_, p, path) self.__addVCSStatus(itm, path) if self.inRefresh: self._addItem(itm, olditem) else: if olditem == self.rootItem: oldindex = QModelIndex() else: oldindex = self.createIndex( olditem.row(), 0, olditem) self.addItem(itm, oldindex) else: if type_ and type_ not in itm.getProjectTypes(): itm.addProjectType(type_) index = self.createIndex(itm.row(), 0, itm) self.dataChanged.emit(index, index) olditem = itm return (itm, pathlist[-1]) else: return (self.rootItem, name) def findChildItem(self, text, column, parentItem=None): """ Public method to find a child item given some text. @param text text to search for (string) @param column column to search in (integer) @param parentItem reference to parent item @return reference to the item found """ if parentItem is None: parentItem = self.rootItem for itm in parentItem.children(): if itm.data(column) == text: return itm return None def addNewItem(self, typeString, name, additionalTypeStrings=[]): """ Public method to add a new item to the model. @param typeString string denoting the type of the new item (string) @param name name of the new item (string) @param additionalTypeStrings names of additional types (list of string) """ # Show the entry in bold in the others browser to make it more # distinguishable if typeString == "OTHERS": bold = True else: bold = False fname = os.path.join(self.project.ppath, name) parentItem, dt = self.findParentItemByName( self.projectBrowserTypes[typeString], name) if parentItem == self.rootItem: parentIndex = QModelIndex() else: parentIndex = self.createIndex(parentItem.row(), 0, parentItem) if os.path.isdir(fname): itm = ProjectBrowserDirectoryItem( parentItem, fname, self.projectBrowserTypes[typeString], False, bold) else: if typeString == "SOURCES": sourceLanguage = self.project.pdata["PROGLANGUAGE"][0] else: sourceLanguage = "" itm = ProjectBrowserFileItem( parentItem, fname, self.projectBrowserTypes[typeString], False, bold, sourceLanguage=sourceLanguage) self.__addVCSStatus(itm, fname) if additionalTypeStrings: for additionalTypeString in additionalTypeStrings: type_ = self.projectBrowserTypes[additionalTypeString] itm.addProjectType(type_) self.addItem(itm, parentIndex) def renameItem(self, name, newFilename): """ Public method to rename an item. @param name the old display name (string) @param newFilename new filename of the item (string) """ itm = self.findItem(name) if itm is None: return index = self.createIndex(itm.row(), 0, itm) itm.setName(newFilename) self.dataChanged.emit(index, index) self.repopulateItem(newFilename) def findItem(self, name): """ Public method to find an item given its name. @param name name of the item (string) @return reference to the item found """ if QDir.isAbsolutePath(name): name = self.project.getRelativePath(name) pathlist = re.split(r'/|\\', name) if len(pathlist) > 0: olditem = self.rootItem for p in pathlist: itm = self.findChildItem(p, 0, olditem) if itm is None: return None olditem = itm return itm else: return None def itemIndexByName(self, name): """ Public method to find an item's index given its name. @param name name of the item (string) @return index of the item found (QModelIndex) """ itm = self.findItem(name) if itm is None: index = QModelIndex() else: index = self.createIndex(itm.row(), 0, itm) return index def itemIndexByNameAndLine(self, name, lineno): """ Public method to find an item's index given its name. @param name name of the item (string) @param lineno one based line number of the item (integer) @return index of the item found (QModelIndex) """ index = QModelIndex() itm = self.findItem(name) if itm is not None and \ isinstance(itm, ProjectBrowserFileItem): olditem = itm autoPopulate = Preferences.getProject("AutoPopulateItems") while itm is not None: if not itm.isPopulated(): if itm.isLazyPopulated() and autoPopulate: self.populateItem(itm) else: break for child in itm.children(): try: start, end = child.boundaries() if end == -1: end = 1000000 # assume end of file if start <= lineno <= end: itm = child break except AttributeError: pass else: itm = None if itm: olditem = itm index = self.createIndex(olditem.row(), 0, olditem) return index def directoryChanged(self, path): """ Public slot to handle the directoryChanged signal of the watcher. @param path path of the directory (string) """ if path not in self.watchedItems: # just ignore the situation we don't have a reference to the item return if Preferences.getUI("BrowsersListHiddenFiles"): filter = QDir.Filters(QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot) else: filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot) for itm in self.watchedItems[path]: oldCnt = itm.childCount() qdir = QDir(itm.dirName()) entryInfoList = qdir.entryInfoList(filter) # step 1: check for new entries children = itm.children() for f in entryInfoList: fpath = Utilities.toNativeSeparators(f.absoluteFilePath()) childFound = False for child in children: if child.name() == fpath: childFound = True children.remove(child) break if childFound: continue cnt = itm.childCount() self.beginInsertRows( self.createIndex(itm.row(), 0, itm), cnt, cnt) if f.isDir(): node = ProjectBrowserDirectoryItem( itm, Utilities.toNativeSeparators(f.absoluteFilePath()), itm.getProjectTypes()[0], False) else: node = ProjectBrowserFileItem( itm, Utilities.toNativeSeparators(f.absoluteFilePath()), itm.getProjectTypes()[0]) self._addItem(node, itm) if self.project.vcs is not None: self.project.vcs.clearStatusCache() state = self.project.vcs.vcsRegisteredState(node.name()) if state == self.project.vcs.canBeCommitted: node.addVcsStatus(self.project.vcs.vcsName()) else: node.addVcsStatus(self.tr("local")) self.endInsertRows() # step 2: check for removed entries if len(entryInfoList) != itm.childCount(): for row in range(oldCnt - 1, -1, -1): child = itm.child(row) childname = Utilities.fromNativeSeparators(child.name()) entryFound = False for f in entryInfoList: if f.absoluteFilePath() == childname: entryFound = True entryInfoList.remove(f) break if entryFound: continue self._removeWatchedItem(child) self.beginRemoveRows( self.createIndex(itm.row(), 0, itm), row, row) itm.removeChild(child) self.endRemoveRows() def __addVCSStatus(self, item, name): """ Private method used to set the vcs status of a node. @param item item to work on @param name filename belonging to this item (string) """ if self.project.vcs is not None: state = self.project.vcs.vcsRegisteredState(name) if state == self.project.vcs.canBeCommitted: item.addVcsStatus(self.project.vcs.vcsName()) else: item.addVcsStatus(self.tr("local")) else: item.addVcsStatus("") def __updateVCSStatus(self, item, name, recursive=True): """ Private method used to update the vcs status of a node. @param item item to work on @param name filename belonging to this item (string) @keyparam recursive flag indicating a recursive update (boolean) """ if self.project.vcs is not None: self.project.vcs.clearStatusCache() state = self.project.vcs.vcsRegisteredState(name) if state == self.project.vcs.canBeCommitted: item.setVcsStatus(self.project.vcs.vcsName()) else: item.setVcsStatus(self.tr("local")) if recursive: name = os.path.dirname(name) parentItem = item.parent() if name and parentItem is not self.rootItem: self.__updateVCSStatus(parentItem, name, recursive) else: item.setVcsStatus("") index = self.createIndex(item.row(), 0, item) self.dataChanged.emit(index, index) def updateVCSStatus(self, name, recursive=True): """ Public method used to update the vcs status of a node. @param name filename belonging to this item (string) @param recursive flag indicating a recursive update (boolean) """ item = self.findItem(name) if item: self.__updateVCSStatus(item, name, recursive) def removeItem(self, name): """ Public method to remove a named item. @param name file or directory name of the item (string). """ fname = os.path.basename(name) parentItem = self.findParentItemByName(0, name)[0] if parentItem == self.rootItem: parentIndex = QModelIndex() else: parentIndex = self.createIndex(parentItem.row(), 0, parentItem) childItem = self.findChildItem(fname, 0, parentItem) if childItem is not None: self.beginRemoveRows(parentIndex, childItem.row(), childItem.row()) parentItem.removeChild(childItem) self.endRemoveRows() def repopulateItem(self, name): """ Public method to repopulate an item. @param name name of the file relative to the project root (string) """ itm = self.findItem(name) if itm is None: return if itm.isLazyPopulated(): if not itm.isPopulated(): # item is not populated yet, nothing to do return if itm.childCount(): index = self.createIndex(itm.row(), 0, itm) self.beginRemoveRows(index, 0, itm.childCount() - 1) itm.removeChildren() self.endRemoveRows() Utilities.ModuleParser.resetParsedModule( os.path.join(self.project.ppath, name)) self.populateItem(itm, True) def projectPropertiesChanged(self): """ Public method to react on a change of the project properties. """ # nothing to do for now return def changeVCSStates(self, statesList): """ Public slot to record the (non normal) VCS states. @param statesList list of VCS state entries (list of strings) giving the states in the first column and the path relative to the project directory starting with the third column. The allowed status flags are: <ul> <li>"A" path was added but not yet comitted</li> <li>"M" path has local changes</li> <li>"O" path was removed</li> <li>"R" path was deleted and then re-added</li> <li>"U" path needs an update</li> <li>"Z" path contains a conflict</li> <li>" " path is back at normal</li> </ul> """ statesList.sort() lastHead = "" itemCache = {} if len(statesList) == 1 and statesList[0] == '--RESET--': statesList = [] for name in list(self.__vcsStatus.keys()): statesList.append(" {0}".format(name)) for name in statesList: state = name[0] name = name[1:].strip() if state == ' ': if name in self.__vcsStatus: del self.__vcsStatus[name] else: self.__vcsStatus[name] = state try: itm = itemCache[name] except KeyError: itm = self.findItem(name) if itm: itemCache[name] = itm if itm: itm.setVcsState(state) itm.setVcsStatus(self.project.vcs.vcsName()) index1 = self.createIndex(itm.row(), 0, itm) index2 = self.createIndex( itm.row(), self.rootItem.columnCount(), itm) self.dataChanged.emit(index1, index2) head, tail = os.path.split(name) if head != lastHead: if lastHead: self.__changeParentsVCSState(lastHead, itemCache) lastHead = head if lastHead: self.__changeParentsVCSState(lastHead, itemCache) try: globalVcsStatus = sorted(self.__vcsStatus.values())[-1] except IndexError: globalVcsStatus = ' ' self.vcsStateChanged.emit(globalVcsStatus) def __changeParentsVCSState(self, path, itemCache): """ Private method to recursively change the parents VCS state. @param path pathname of parent item (string) @param itemCache reference to the item cache used to store references to named items """ while path: try: itm = itemCache[path] except KeyError: itm = self.findItem(path) if itm: itemCache[path] = itm if itm: state = " " for id_ in itm.children(): if state < id_.vcsState: state = id_.vcsState if state != itm.vcsState: itm.setVcsState(state) index1 = self.createIndex(itm.row(), 0, itm) index2 = self.createIndex( itm.row(), self.rootItem.columnCount(), itm) self.dataChanged.emit(index1, index2) path, tail = os.path.split(path) def preferencesChanged(self): """ Public method used to handle a change in preferences. """ for code in list(self.colorNames.keys()): color = Preferences.getProjectBrowserColour(self.colorNames[code]) if color.name() == self.itemBackgroundColors[code].name(): continue self.itemBackgroundColors[code] = color color = Preferences.getProjectBrowserColour("Highlighted") if self.highLightColor.name() != color.name(): self.highLightColor = color
def __init__(self): super(PugdebugDocuments, self).__init__() self.watcher = QFileSystemWatcher() self.watcher.fileChanged.connect(self.handle_file_changed)
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.centralwidget.hide() self.parser = argparse.ArgumentParser("playground") self.parser.add_argument("-d", "--data-dir", type=str, help="data dir") self.parser.add_argument("-a", "--console-address", type=str, help="console address", default='localhost') self.parser.add_argument("-p", "--console-port", type=int, help="console port", default=2222) self.args = self.parser.parse_args() self.data_dir = self.args.data_dir self.console_port = self.args.console_port self.console_address = self.args.console_address self.project = CetechProject() self.api = QtConsoleAPI(self.console_address, self.console_port) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) self.script_editor_widget = ScriptEditor(project_manager=self.project, api=self.api) self.script_editor_dock_widget = QDockWidget(self) self.script_editor_dock_widget.setWindowTitle("Script editor") self.script_editor_dock_widget.hide() self.script_editor_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.script_editor_dock_widget.setWidget(self.script_editor_widget) self.addDockWidget(Qt.TopDockWidgetArea, self.script_editor_dock_widget) self.log_widget = LogWidget(self.api, self.script_editor_widget) self.log_dock_widget = QDockWidget(self) self.log_dock_widget.hide() self.log_dock_widget.setWindowTitle("Log") self.log_dock_widget.setWidget(self.log_widget) self.addDockWidget(Qt.BottomDockWidgetArea, self.log_dock_widget) self.assetb_widget = AssetBrowser() self.assetb_dock_widget = QDockWidget(self) self.assetb_dock_widget.hide() self.assetb_dock_widget.setWindowTitle("Asset browser") self.assetb_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.assetb_dock_widget.setWidget(self.assetb_widget) self.addDockWidget(Qt.LeftDockWidgetArea, self.assetb_dock_widget) self.recorded_event_widget = RecordEventWidget(api=self.api) self.recorded_event_dock_widget = QDockWidget(self) self.recorded_event_dock_widget.setWindowTitle("Recorded events") self.recorded_event_dock_widget.hide() self.recorded_event_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.recorded_event_dock_widget.setWidget(self.recorded_event_widget) self.addDockWidget(Qt.RightDockWidgetArea, self.recorded_event_dock_widget) #TODO bug #114 workaround. Disable create sub engine... if platform.system().lower() != 'darwin': self.ogl_widget = CetechWidget(self, self.api) self.ogl_dock = QDockWidget(self) self.ogl_dock.hide() self.ogl_dock.setWidget(self.ogl_widget) self.addDockWidget(Qt.TopDockWidgetArea, self.ogl_dock) self.tabifyDockWidget(self.assetb_dock_widget, self.log_dock_widget) self.assetb_widget.asset_clicked.connect(self.open_asset) self.file_watch = QFileSystemWatcher(self) self.file_watch.fileChanged.connect(self.file_changed) self.file_watch.directoryChanged.connect(self.dir_changed) self.build_file_watch = QFileSystemWatcher(self) self.build_file_watch.fileChanged.connect(self.build_file_changed) self.build_file_watch.directoryChanged.connect(self.build_dir_changed) def open_asset(self, path, ext): if self.script_editor_widget.support_ext(ext): self.script_editor_widget.open_file(path) self.script_editor_dock_widget.show() self.script_editor_dock_widget.focusWidget() def open_project(self, name, dir): self.project.open_project(name, dir) # self.project.run_cetech(build_type=CetechProject.BUILD_DEBUG, compile=True, continu=True, daemon=True) if platform.system().lower() == 'darwin': wid = None else: wid = self.ogl_widget.winId() self.project.run_cetech(build_type=CetechProject.BUILD_DEBUG, compile_=True, continue_=True, wid=wid) self.api.start(QThread.LowPriority) self.assetb_widget.open_project(self.project.project_dir) self.assetb_dock_widget.show() self.log_dock_widget.show() #TODO bug #114 workaround. Disable create sub engine... if platform.system().lower() != 'darwin': self.ogl_dock.show() self.watch_project_dir() def watch_project_dir(self): files = self.file_watch.files() directories = self.file_watch.directories() if len(files): self.file_watch.removePaths(files) if len(directories): self.file_watch.removePaths(directories) files = self.build_file_watch.files() directories = self.build_file_watch.directories() if len(files): self.build_file_watch.removePaths(files) if len(directories): self.build_file_watch.removePaths(directories) files = [] it = QDirIterator(self.project.source_dir, QDirIterator.Subdirectories) while it.hasNext(): files.append(it.next()) self.file_watch.addPaths(files) files = [] it = QDirIterator(self.project.build_dir, QDirIterator.Subdirectories) while it.hasNext(): files.append(it.next()) self.build_file_watch.addPaths(files) def file_changed(self, path): self.api.compile_all() def dir_changed(self, path): self.watch_project_dir() def build_file_changed(self, path): self.api.autocomplete_list() def build_dir_changed(self, path): pass def open_script_editor(self): self.script_editor_dock_widget.show() def open_recorded_events(self): self.recorded_event_dock_widget.show() def closeEvent(self, evnt): self.api.disconnect() self.project.killall_process() self.statusbar.showMessage("Disconnecting ...") while self.api.connected: self.api.tick() self.statusbar.showMessage("Disconnected") evnt.accept()
def __init__(self, **kwds): super(ExecuteOptionsPlugin, self).__init__(**kwds) self._preferences.addInt("execute/maxRecentWorkingDirs", "Max recent working directories", 10, 1, 50, "Set the maximum number of recent working directories that have been used.", ) self._preferences.addInt("execute/maxRecentExes", "Max recent executables", 10, 1, 50, "Set the maximum number of recent executables that have been used.", ) self._preferences.addInt("execute/maxRecentArgs", "Max recent command line arguments", 10, 1, 50, "Set the maximum number of recent command line arguments that have been used.", ) self._preferences.addBool("execute/allowTestObjects", "Allow using test objects", False, "Allow using test objects by default", ) self._preferences.addBool("execute/mpiEnabled", "Enable MPI by default", False, "Set the MPI checkbox on by default", ) self._preferences.addString("execute/mpiArgs", "Default mpi command", "mpiexec -n 2", "Set the default MPI command to run", ) self._preferences.addBool("execute/threadsEnabled", "Enable threads by default", False, "Set the threads checkbox on by default", ) self._preferences.addString("execute/threadsArgs", "Default threads arguments", "--n-threads=2", "Set the default threads arguments", ) self.all_exe_layout = WidgetUtils.addLayout(grid=True) self.setLayout(self.all_exe_layout) self.working_label = WidgetUtils.addLabel(None, self, "Working Directory") self.all_exe_layout.addWidget(self.working_label, 0, 0) self.choose_working_button = WidgetUtils.addButton(None, self, "Choose", self._chooseWorkingDir) self.all_exe_layout.addWidget(self.choose_working_button, 0, 1) self.working_line = WidgetUtils.addLineEdit(None, self, None, readonly=True) self.working_line.setText(os.getcwd()) self.all_exe_layout.addWidget(self.working_line, 0, 2) self.exe_label = WidgetUtils.addLabel(None, self, "Executable") self.all_exe_layout.addWidget(self.exe_label, 1, 0) self.choose_exe_button = WidgetUtils.addButton(None, self, "Choose", self._chooseExecutable) self.all_exe_layout.addWidget(self.choose_exe_button, 1, 1) self.exe_line = WidgetUtils.addLineEdit(None, self, None, readonly=True) self.all_exe_layout.addWidget(self.exe_line, 1, 2) self.args_label = WidgetUtils.addLabel(None, self, "Extra Arguments") self.all_exe_layout.addWidget(self.args_label, 2, 0) self.args_line = WidgetUtils.addLineEdit(None, self, None) self.all_exe_layout.addWidget(self.args_line, 2, 2) self.test_label = WidgetUtils.addLabel(None, self, "Allow test objects") self.all_exe_layout.addWidget(self.test_label, 3, 0) self.test_checkbox = WidgetUtils.addCheckbox(None, self, "", self._allowTestObjects) self.test_checkbox.setChecked(self._preferences.value("execute/allowTestObjects")) self.all_exe_layout.addWidget(self.test_checkbox, 3, 1, alignment=Qt.AlignHCenter) self.mpi_label = WidgetUtils.addLabel(None, self, "Use MPI") self.all_exe_layout.addWidget(self.mpi_label, 4, 0) self.mpi_checkbox = WidgetUtils.addCheckbox(None, self, "", None) self.mpi_checkbox.setChecked(self._preferences.value("execute/mpiEnabled")) self.all_exe_layout.addWidget(self.mpi_checkbox, 4, 1, alignment=Qt.AlignHCenter) self.mpi_line = WidgetUtils.addLineEdit(None, self, None) self.mpi_line.setText(self._preferences.value("execute/mpiArgs")) self.mpi_line.cursorPositionChanged.connect(self._mpiLineCursorChanged) self.all_exe_layout.addWidget(self.mpi_line, 4, 2) self.threads_label = WidgetUtils.addLabel(None, self, "Use Threads") self.all_exe_layout.addWidget(self.threads_label, 5, 0) self.threads_checkbox = WidgetUtils.addCheckbox(None, self, "", None) self.threads_checkbox.setChecked(self._preferences.value("execute/threadsEnabled")) self.all_exe_layout.addWidget(self.threads_checkbox, 5, 1, alignment=Qt.AlignHCenter) self.threads_line = WidgetUtils.addLineEdit(None, self, None) self.threads_line.setText(self._preferences.value("execute/threadsArgs")) self.threads_line.cursorPositionChanged.connect(self._threadsLineCursorChanged) self.all_exe_layout.addWidget(self.threads_line, 5, 2) self.csv_label = WidgetUtils.addLabel(None, self, "Postprocessor CSV Output") self.all_exe_layout.addWidget(self.csv_label, 6, 0) self.csv_checkbox = WidgetUtils.addCheckbox(None, self, "", None) self.all_exe_layout.addWidget(self.csv_checkbox, 6, 1, alignment=Qt.AlignHCenter) self.csv_checkbox.setCheckState(Qt.Checked) self.recover_label = WidgetUtils.addLabel(None, self, "Recover") self.all_exe_layout.addWidget(self.recover_label, 7, 0) self.recover_checkbox = WidgetUtils.addCheckbox(None, self, "", None) self.all_exe_layout.addWidget(self.recover_checkbox, 7, 1, alignment=Qt.AlignHCenter) self._recent_exe_menu = None self._recent_working_menu = None self._recent_args_menu = None self._exe_watcher = QFileSystemWatcher() self._exe_watcher.fileChanged.connect(self.setExecutablePath) self._loading_dialog = QMessageBox(parent=self) self._loading_dialog.setWindowTitle("Loading executable") self._loading_dialog.setStandardButtons(QMessageBox.NoButton) # get rid of the OK button self._loading_dialog.setWindowModality(Qt.ApplicationModal) self._loading_dialog.setIcon(QMessageBox.Information) self._loading_dialog.setText("Loading executable") self.setup()