def get(name: str) -> str: """Return style option for a given name.""" try: return _style["{%s}" % (name)] except KeyError: log.error("Style option '%s' not found, falling back to default", name) return ""
def enter(self) -> None: """Override enter to ensure we are not in read-only mode when manipulating.""" if settings.read_only: log.error( "Manipulate mode is disabled due to read-only being active") else: super().enter()
def _run_command(count: str, cmdname: str, args: List[str], mode: api.modes.Mode) -> None: """Run a given command. Args: count: Count to use for the command. cmdname: Name of the command passed. args: Arguments passed. mode: Mode to run the command in. """ try: cmd = api.commands.get(cmdname, mode) if cmd.store: _last_command[mode] = LastCommand(count, cmdname, args) cmd(args, count=count) api.status.update("ran command") except api.commands.CommandNotFound as e: log.error(str(e)) raise CommandPartFailed from e except ( api.commands.ArgumentError, api.commands.CommandError, api.modes.InvalidMode, ) as e: log.error("%s: %s", cmdname, e) raise CommandPartFailed from e except api.commands.CommandWarning as w: log.warning("%s: %s", cmdname, str(w)) raise CommandPartFailed from w except api.commands.CommandInfo as i: log.info("%s: %s", cmdname, i) raise CommandPartFailed from i
def _on_new_images_opened(self, paths: List[str]): """Load new paths into thumbnail widget. Args: paths: List of new paths to load. """ if paths == self._paths: # Nothing to do _logger.debug("No new images to load") return _logger.debug("Loading %d new images", len(paths)) # Delete paths that are no longer here # We must go in reverse order as otherwise the indexing changes on the # fly for i, path in enumerate(self._paths[::-1]): if path not in paths: _logger.debug("Removing existing thumbnail '%s'", path) if not self.takeItem(len(self._paths) - 1 - i): log.error("Error removing thumbnail for %s", path) # Add new paths size_hint = QSize(self.item_size(), self.item_size()) for i, path in enumerate(paths): if path not in self._paths: _logger.debug("Adding new thumbnail '%s'", path) marked = path in api.mark.paths ThumbnailItem(self, i, marked=marked, size_hint=size_hint) # Update paths and create thumbnails self._paths = paths self._manager.create_thumbnails_async(paths)
def __init__(self, filename): super().__init__() self._exif = None if piexif is None: log.error("%s relies on exif support", self.__class__.__qualname__) else: self._load_exif(filename)
def _monitor(self, directory: str) -> None: """Monitor the directory by adding it to QFileSystemWatcher.""" if not settings.monitor_fs.value: return if not self.addPath(directory): log.error("Cannot monitor %s", directory) else: _logger.debug("Monitoring %s", directory)
def _add_aliases(configsection): """Add optional aliases defined in the alias section to AliasRunner. Args: configsection: ALIASES section in the config file. """ for name, command in configsection.items(): try: aliases.alias(name, [command], "global") except api.commands.CommandError as e: log.error("Reading aliases from config: %s", str(e))
def _on_finished(self, exitcode, exitstatus): """Check exit status and possibly process standard output on completion.""" if exitstatus != QProcess.NormalExit or exitcode != 0: log.error( "Error running external process '%s':\n%s", self.program(), qbytearray_to_str(self.readAllStandardError()).strip(), ) elif self._pipe: self._process_pipe() else: _logger.debug("Finished external process '%s' succesfully", self.program())
def copy_image( primary: bool = False, width: int = None, height: int = None, size: int = None, count: int = None, ) -> None: """Copy currently selected image to system clipboard. **syntax:** ``:copy-image [--primary] [--width=WIDTH] [--height=HEIGHT] [--size=SIZE]`` optional arguments: * ``--primary``: Copy to primary selection. * ``--width``: Scale width to the specified value. * ``--height``: Scale height to the specified value. * ``--size``: Scale longer side to the specified value. **count:** Equivalent to the ``--size`` option """ clipboard = QGuiApplication.clipboard() mode = QClipboard.Selection if primary else QClipboard.Clipboard path = api.current_path() try: reader = imagereader.get_reader(path) pixmap = reader.get_pixmap() except ValueError as e: log.error(str(e)) return if size or count: pix_size = pixmap.size() size = count if count is not None else size if pix_size.height() >= pix_size.width(): _logger.debug(f"Copy image with size {size} restricting height") pixmap = pixmap.scaledToHeight(size) # type: ignore[arg-type] else: _logger.debug(f"Copy image with size {size} restricting width") pixmap = pixmap.scaledToWidth(size) # type: ignore[arg-type] elif width: _logger.debug(f"Copy image with width {width}") pixmap = pixmap.scaledToWidth(width) elif height: _logger.debug(f"Copy image with height {height}") pixmap = pixmap.scaledToHeight(height) clipboard.setPixmap(pixmap, mode=mode)
def chdir(self, directory: str, reload_current: bool = False) -> None: """Change the current working directory to directory.""" directory = os.path.abspath(directory) if directory != self._dir or reload_current: _logger.debug("Changing directory to '%s'", directory) if self.directories(): # Unmonitor old directories self.removePaths(self.directories()) try: os.chdir(directory) self._load_directory(directory) self._monitor(directory) except PermissionError as e: log.error("%s: Cannot access '%s'", str(e), directory) else: _logger.debug("Directory change completed")
def update_settings(args: argparse.Namespace) -> None: """Update default settings with command line arguments and configfiles. Args: args: Arguments returned from parser.parse_args(). """ configfile.parse(args.config) for option, value in args.cmd_settings: try: setting = api.settings.get(option) setting.value = value except KeyError: log.error("Unknown setting %s", option) except ValueError as e: log.error(str(e)) keyfile.parse(args.keyfile) styles.parse()
def command(text: str, mode: api.modes.Mode = None) -> None: """Run internal command when called. Splits the given text into count, name and arguments. Then runs the command corresponding to name with count and arguments. Emits the exited signal when done. Args: text: String passed as command. mode: Mode in which the command is supposed to run. """ try: count, cmdname, args = _parse(text) except ValueError as e: # E.g. raised by shlex on unclosed quotation log.error("Error parsing command: %s", e) return mode = mode if mode is not None else api.modes.current() _run_command(count, cmdname, args, mode) _logger.debug("Ran '%s' succesfully", text)
def parse(): """Setup the style. Checks for a style name and reads it from file. If the name is default, the defaults are simply used and the default style file is written to disk for reference. """ global _style name = api.settings.style.value _logger.debug("Parsing style '%s'", name) filename = abspath(name) if name == NAME_DEFAULT: _style = create_default() elif name == NAME_DEFAULT_DARK: _style = create_default(dark=True) elif os.path.exists(filename): _style = read(filename) else: log.error("Style file '%s' not found, falling back to default", filename) _style = create_default()
def _load_plugin(name: str, info: str, directory: str) -> None: """Load a single plugin. Args: name: Name of the plugin as python module. info: Additional information string passed to the plugin's init. directory: Directory in which the python module is located. """ _logger.debug("Loading plugin '%s' from '%s'", name, directory) try: module = importlib.import_module(name, directory) except (ImportError, SyntaxError) as e: log.error("Importing plugin '%s': %s", name, str(e)) return try: module.init(info) _logger.debug("Initialized '%s'", name) except AttributeError: _logger.debug("Plugin '%s' does not define init()", name) _logger.debug("Loaded '%s' successfully", name) _loaded_plugins[name] = module
def _load(self, path: str, keep_zoom: bool): """Load proper displayable QWidget for a path. This reads the image using QImageReader and then emits the appropriate *_loaded signal to tell the image to display a new object. """ try: reader = imagereader.get_reader(path) except ValueError as e: log.error(str(e)) return # SVG if reader.is_vectorgraphic and QtSvg is not None: # Do not store image and only emit with the path as the # VectorGraphic widget needs the path in the constructor api.signals.svg_loaded.emit(path, keep_zoom) self._edit_handler.clear() # Gif elif reader.is_animation: movie = QMovie(path) if not movie.isValid() or movie.frameCount() == 0: log.error("Error reading animation %s: invalid data", path) return api.signals.movie_loaded.emit(movie, keep_zoom) self._edit_handler.clear() # Regular image else: try: pixmap = reader.get_pixmap() except ValueError as e: log.error("%s", e) return self._edit_handler.pixmap = pixmap api.signals.pixmap_loaded.emit(pixmap, keep_zoom) self._path = path
def handle_exception( self, initial_handler: ExceptionHandler, exc_type: Type[BaseException], exc_value: BaseException, traceback: types.TracebackType, ) -> None: """Custom exception handler for uncaught exceptions. In addition to the standard python exception handler a log message is called and the application tries to exit gracefully. """ log.error("Uncaught exception! Exiting gracefully and printing stack...") initial_handler(exc_type, exc_value, traceback) try: self._app.exit(customtypes.Exit.err_exception) # We exit immediately by killing the application if an error in the graceful # exit occurs except Exception as e: # pylint: disable=broad-except log.fatal("Uncaught exception in graceful exit... Committing suicide :(") log.fatal("Exception: %r", e) sys.exit(customtypes.Exit.err_suicide)
def write_pixmap(pixmap, path, original_path): """Write pixmap to file. This requires both the path to write to and the original path as Exif data may be copied from the original path to the new copy. The procedure is to write the path to a temporary file first, transplant the Exif data to the temporary file if possible and finally rename the temporary file to the final path. The renaming is done as it is an atomic operation and we may be overriding the existing file. Args: pixmap: The QPixmap to write. path: Path to write the pixmap to. original_path: Original path of the opened pixmap to retrieve exif information. """ try: _can_write(pixmap, path) _logger.debug("Image is writable") _write(pixmap, path, original_path) log.info("Saved %s", path) except WriteError as e: log.error(str(e))
def _load(self, path: str, reload_only: bool): """Load proper displayable QWidget for a path. This reads the image using QImageReader and then emits the appropriate *_loaded signal to tell the image to display a new object. """ # Pass file format explicitly as imghdr does a much better job at this than the # file name based approach of QImageReader file_format = files.imghdr.what(path) if file_format is None: log.error("%s is not a valid image", path) return reader = QImageReader(path, file_format.encode("utf-8")) reader.setAutoTransform(True) # Automatically apply exif orientation if not reader.canRead(): log.error("Cannot read image %s", path) return # SVG if file_format == "svg" and QSvgWidget: # Do not store image and only emit with the path as the # VectorGraphic widget needs the path in the constructor self.original = None api.signals.svg_loaded.emit(path, reload_only) self._image_type = ImageType.Svg # Gif elif reader.supportsAnimation(): movie = QMovie(path) if not movie.isValid() or movie.frameCount() == 0: log.error("Error reading animation %s: invalid data", path) return self.original = movie api.signals.movie_loaded.emit(self.current, reload_only) self._image_type = ImageType.Movie # Regular image else: pixmap = QPixmap.fromImageReader(reader) if reader.error(): log.error("Error reading image %s: %s", path, reader.errorString()) return self.original = pixmap api.signals.pixmap_loaded.emit(self.current, reload_only) self._image_type = ImageType.Pixmap self._path = path
def _on_error(self, error): log.error("Error running '%s': %s", self.program(), self.error_messages[error])