/
Ig.Ro
/
PDF_View
Обзор
Документация
Войти
/
Ig.Ro
/
PDF_View
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
viewer_window.py
902 строки
38 KB
Ig.Ro
upload files
14 янв 2026, 16:44
14 янв 2026, 16:44
2d33e26
Код
Авторство
О чём код?
import os import json import logging from pathlib import Path from PyQt6.QtWidgets import ( QMainWindow, QLabel, QToolBar, QToolButton, QMessageBox, QStatusBar, QFileDialog, QWidget, QHBoxLayout, QSpinBox, QTabWidget, QProgressDialog, QStyle, QDialog ) from PyQt6.QtGui import QIcon, QKeySequence, QAction, QPainter from PyQt6.QtCore import ( Qt, QThread, pyqtSlot, QTimer, QSize ) from PyQt6.QtPrintSupport import QPrinter, QPrintDialog, QPrintPreviewDialog import pdf_viewer import render_worker import utils logger = logging.getLogger("PDFViewer") class ViewerWindow(QMainWindow): def __init__(self, file_path=None): super().__init__() self.setWindowTitle("PDF Viewer") self.resize(1000, 700) self.poppler_path = utils.ensure_poppler() self._is_loading = False self._current_dimensions = None self._restoring_tabs = False # Флаг для отслеживания восстановления вкладок self.settings = utils.QSettings("MyCompany", "PDFViewer") self._cache_pages = self.settings.value("cache_pages", True, type=bool) self._title_block_enabled = self.settings.value("title_block_enabled", True, type=bool) self._title_block_scale = self.settings.value("title_block_scale", 200, type=int) self._limit_tabs = self.settings.value("limit_tabs", True, type=bool) self._max_tabs = self.settings.value("max_tabs", 10, type=int) self._close_first_tab = self.settings.value("close_first_tab", False, type=bool) self._restore_tabs = self.settings.value("restore_tabs", True, type=bool) # Новая настройка self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("Готов к работе", 3000) self.tab_widget = QTabWidget() self.tab_widget.setTabsClosable(True) self.tab_widget.tabCloseRequested.connect(self.close_tab) self.tab_widget.currentChanged.connect(self.on_tab_changed) self.setCentralWidget(self.tab_widget) self.tabs = {} self.create_new_tab() self.toolbar = QToolBar() self.toolbar.setIconSize(QSize(20, 20)) self.addToolBar(self.toolbar) icon_map = { "document-open": QStyle.StandardPixmap.SP_DialogOpenButton, "zoom-in": QStyle.StandardPixmap.SP_ArrowUp, "zoom-out": QStyle.StandardPixmap.SP_ArrowDown, "view-refresh": QStyle.StandardPixmap.SP_BrowserReload, "view-fullscreen": QStyle.StandardPixmap.SP_TitleBarMaxButton, "object-rotate-right": QStyle.StandardPixmap.SP_ArrowRight, "object-rotate-left": QStyle.StandardPixmap.SP_ArrowLeft, "zoom-fit-best": QStyle.StandardPixmap.SP_DialogYesButton, "document-properties": QStyle.StandardPixmap.SP_FileDialogInfoView, "preferences-system": QStyle.StandardPixmap.SP_FileDialogDetailedView, "go-previous": QStyle.StandardPixmap.SP_ArrowBack, "go-next": QStyle.StandardPixmap.SP_ArrowForward, "go-first": QStyle.StandardPixmap.SP_MediaSkipBackward, "go-last": QStyle.StandardPixmap.SP_MediaSkipForward, "document-print": QStyle.StandardPixmap.SP_FileDialogStart, } toolbar_buttons = [ ("document-open", self.open_pdf_dialog, "Открыть PDF (Ctrl+O)"), ("zoom-in", self.zoom_in_current, "Увеличить (+)"), ("zoom-out", self.zoom_out_current, "Уменьшить (-)"), ("view-refresh", self.reset_zoom_current, "100% (0)"), ("view-fullscreen", self.fit_width_current, "По ширине (F)"), ("zoom-fit-best", self.fit_window_current, "Вписать в окно (W)"), ("document-properties", self.show_pdf_size, "Размеры PDF (S)"), ("object-rotate-right", self.rotate_clockwise_current, "→ (R)"), ("object-rotate-left", self.rotate_counterclockwise_current, "← (E)"), ("document-print", self.print_document, "Печать (Ctrl+P)"), ("preferences-system", self.open_settings, "Настройки"), ] for name, slot, tip in toolbar_buttons: btn = QToolButton() icon = QIcon.fromTheme(name) if icon.isNull(): std_icon = icon_map.get(name) if std_icon: icon = self.style().standardIcon(std_icon) btn.setIcon(icon) btn.setToolTip(tip) btn.clicked.connect(slot) self.toolbar.addWidget(btn) self.toolbar.addSeparator() self.create_page_navigation_widgets() app_icon = QIcon.fromTheme("document-viewer") if app_icon.isNull(): app_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogContentsView) self.setWindowIcon(app_icon) self.open_shortcut = self.create_shortcut("Ctrl+O", self.open_pdf_dialog) self.settings_shortcut = self.create_shortcut("Ctrl+Shift+P", self.open_settings) self.print_shortcut = self.create_shortcut("Ctrl+P", self.print_document) self.next_page_shortcut = self.create_shortcut("Right", self.next_page_current) self.prev_page_shortcut = self.create_shortcut("Left", self.prev_page_current) self.first_page_shortcut = self.create_shortcut("Home", self.first_page_current) self.last_page_shortcut = self.create_shortcut("End", self.last_page_current) self.new_tab_shortcut = self.create_shortcut("Ctrl+T", lambda: self.create_new_tab()) self.close_tab_shortcut = self.create_shortcut("Ctrl+W", self.close_current_tab) self.toggle_title_block_shortcut = self.create_shortcut("Ctrl+B", self.toggle_title_block) # Восстанавливаем вкладки при запуске, если включена соответствующая настройка if not file_path and self._restore_tabs: self.restore_tabs_on_startup() auto_open = self.settings.value("auto_open_last_file", True, type=bool) if not file_path: if auto_open and not self._restoring_tabs: last_file = self.settings.value("last_file", "") if last_file and os.path.isfile(last_file): file_path = last_file logger.info(f"Автоматически открываем последний файл: {file_path}") if file_path and not self._restoring_tabs: self.load_pdf(file_path) def activate_window(self): if self.isMinimized(): self.showNormal() self.activateWindow() self.raise_() self.setFocus() logger.debug("Окно активировано") def restore_tabs_on_startup(self): """Восстанавливает вкладки из сохраненных настроек""" try: tabs_data = self.settings.value("saved_tabs", "[]") tabs_list = json.loads(tabs_data) if not tabs_list: logger.info("Нет сохраненных вкладок для восстановления") return logger.info(f"Восстанавливаем {len(tabs_list)} вкладок") self._restoring_tabs = True # Закрываем пустую начальную вкладку if self.tab_widget.count() == 1: tab = self.get_current_tab() if tab and not tab.file_path: self.tab_widget.removeTab(0) if 0 in self.tabs: del self.tabs[0] # Восстанавливаем каждую вкладку for i, tab_info in enumerate(tabs_list): if isinstance(tab_info, dict) and "path" in tab_info: path = tab_info["path"] if os.path.isfile(path): # Создаем вкладку с восстановлением (без проверки лимита) self.create_new_tab(path, restoring=True) else: logger.warning(f"Файл не найден, пропускаем вкладку: {path}") # Создаем пустую вкладку self.create_new_tab(restoring=True) else: # Создаем пустую вкладку self.create_new_tab(restoring=True) # Восстанавливаем активную вкладку active_tab_index = self.settings.value("active_tab_index", 0, type=int) if 0 <= active_tab_index < self.tab_widget.count(): self.tab_widget.setCurrentIndex(active_tab_index) self._restoring_tabs = False logger.info("Вкладки успешно восстановлены") except Exception as e: logger.error(f"Ошибка при восстановлении вкладок: {e}") self._restoring_tabs = False def save_tabs_on_close(self): """Сохраняет информацию о текущих вкладках""" if not self._restore_tabs: logger.info("Восстановление вкладок отключено, пропускаем сохранение") return try: tabs_list = [] for i in range(self.tab_widget.count()): tab = self.tab_widget.widget(i) if hasattr(tab, 'file_path') and tab.file_path and os.path.isfile(tab.file_path): tab_info = { "path": tab.file_path, "title": tab.file_name } tabs_list.append(tab_info) else: # Пустая вкладка tabs_list.append({"path": "", "title": "Новая вкладка"}) # Сохраняем как JSON строку tabs_json = json.dumps(tabs_list) self.settings.setValue("saved_tabs", tabs_json) # Сохраняем индекс активной вкладки active_index = self.tab_widget.currentIndex() self.settings.setValue("active_tab_index", active_index) logger.info(f"Сохранено {len(tabs_list)} вкладок, активная: {active_index}") except Exception as e: logger.error(f"Ошибка при сохранении вкладок: {e}") def create_new_tab(self, file_path=None, restoring=False): # Если не восстановление и есть лимит вкладок, проверяем if not restoring and self._limit_tabs and self.tab_widget.count() >= self._max_tabs: # Если включено автозакрытие первой вкладки if self._close_first_tab: if self.tab_widget.count() > 0: # Закрываем первую вкладку (индекс 0) self.close_tab(0, silent=True) # После закрытия создаем новую вкладку return self._create_tab_after_check(file_path, restoring) else: QMessageBox.warning(self, "Лимит вкладок", f"Достигнут максимальный лимит вкладок ({self._max_tabs}).\n" "Закройте некоторые вкладки перед открытием новых.") return None return self._create_tab_after_check(file_path, restoring) def _create_tab_after_check(self, file_path=None, restoring=False): """Вспомогательный метод для создания вкладки после проверок""" tab = pdf_viewer.PDFTabWidget(file_path, self) tab_name = "Новая вкладка" if not file_path else os.path.basename(file_path) index = self.tab_widget.addTab(tab, tab_name) self.tabs[index] = tab self.tab_widget.setCurrentIndex(index) if file_path and not restoring: self.load_pdf_in_tab(index, file_path, restoring) return index def get_current_tab(self): index = self.tab_widget.currentIndex() if index >= 0: return self.tabs.get(index) return None def get_current_pdf_viewer(self): tab = self.get_current_tab() if tab: return tab.pdf_viewer return None def close_tab(self, index, silent=False): if index < 0 or index >= self.tab_widget.count(): return if not silent: tab = self.tabs.get(index) file_name = tab.file_name if tab and tab.file_name else "Новая вкладка" reply = QMessageBox.question( self, "Закрытие вкладки", f"Вы уверены, что хотите закрыть вкладку \"{file_name}\"?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply != QMessageBox.StandardButton.Yes: return tab = self.tabs.get(index) if tab and tab.pdf_viewer: tab.pdf_viewer.save_state() self.tab_widget.removeTab(index) if index in self.tabs: del self.tabs[index] self.reindex_tabs() # Если закрыли текущую вкладку, обновляем заголовок if self.tab_widget.count() > 0: current_index = self.tab_widget.currentIndex() if current_index >= 0 and current_index in self.tabs: tab = self.tabs[current_index] if tab.file_path: self.setWindowTitle(f"PDF Viewer — {tab.file_name} (Вкладка {current_index + 1})") else: self.create_new_tab() # Обновляем кнопки навигации self.update_navigation_buttons() def close_current_tab(self): index = self.tab_widget.currentIndex() if index >= 0: self.close_tab(index) def reindex_tabs(self): new_tabs = {} for i in range(self.tab_widget.count()): widget = self.tab_widget.widget(i) for old_index, tab in self.tabs.items(): if tab == widget: new_tabs[i] = tab break self.tabs = new_tabs def on_tab_changed(self, index): if index >= 0 and index in self.tabs: tab = self.tabs[index] self.update_navigation_buttons() if tab.file_path: self.setWindowTitle(f"PDF Viewer — {tab.file_name} (Вкладка {index + 1})") self.update_title_block() def on_tab_dimensions_changed(self, tab, dimensions): current_tab = self.get_current_tab() if current_tab == tab: self._current_dimensions = dimensions self._update_status_bar_display(dimensions) def on_tab_page_changed(self, tab, current_page, total_pages): current_tab = self.get_current_tab() if current_tab == tab: self.update_page_navigation(current_page, total_pages) def on_tab_view_updated(self, tab): logger.debug(f"ViewerWindow: получен сигнал on_tab_view_updated для вкладки {tab.file_name}") current_tab = self.get_current_tab() if current_tab == tab: logger.debug(f"ViewerWindow: обновляем основную надпись для текущей вкладки") self.update_title_block() else: logger.debug(f"ViewerWindow: вкладка не является текущей, пропускаем обновление") def create_page_navigation_widgets(self): self.first_page_btn = QToolButton() self.first_page_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaSkipBackward)) self.first_page_btn.setToolTip("Первая страница (Home)") self.first_page_btn.clicked.connect(self.first_page_current) self.toolbar.addWidget(self.first_page_btn) self.prev_page_btn = QToolButton() self.prev_page_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_ArrowBack)) self.prev_page_btn.setToolTip("Предыдущая страница (←)") self.prev_page_btn.clicked.connect(self.prev_page_current) self.toolbar.addWidget(self.prev_page_btn) nav_widget = QWidget() nav_layout = QHBoxLayout() nav_layout.setContentsMargins(5, 0, 5, 0) nav_layout.setSpacing(5) nav_layout.addWidget(QLabel("Страница:")) self.page_spin = QSpinBox() self.page_spin.setMinimum(1) self.page_spin.setMaximum(1) self.page_spin.setValue(1) self.page_spin.setFixedWidth(60) self.page_spin.valueChanged.connect(self.on_page_spin_changed) nav_layout.addWidget(self.page_spin) self.total_pages_label = QLabel("/ 1") nav_layout.addWidget(self.total_pages_label) nav_widget.setLayout(nav_layout) self.toolbar.addWidget(nav_widget) self.next_page_btn = QToolButton() self.next_page_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_ArrowForward)) self.next_page_btn.setToolTip("Следующая страница (→)") self.next_page_btn.clicked.connect(self.next_page_current) self.toolbar.addWidget(self.next_page_btn) self.last_page_btn = QToolButton() self.last_page_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaSkipForward)) self.last_page_btn.setToolTip("Последняя страница (End)") self.last_page_btn.clicked.connect(self.last_page_current) self.toolbar.addWidget(self.last_page_btn) self.toolbar.addSeparator() self.update_navigation_buttons() def update_title_block(self): logger.debug(f"ViewerWindow: обновление основной надписи, включена: {self._title_block_enabled}") tab = self.get_current_tab() if not tab: logger.warning("ViewerWindow: нет текущей вкладки") return if not self._title_block_enabled: logger.debug("ViewerWindow: основная надпись отключена") tab.update_title_block_visibility(False) return if tab.pdf_viewer._original_images: logger.debug(f"ViewerWindow: обновляем основную надпись, масштаб: {self._title_block_scale}%") tab.update_title_block_content(self._title_block_scale) tab.update_title_block_geometry() else: logger.warning("ViewerWindow: нет изображений для отображения основной надписи") tab.update_title_block_visibility(False) def toggle_title_block(self): """Переключение отображения основной надписи""" self._title_block_enabled = not self._title_block_enabled self.settings.setValue("title_block_enabled", self._title_block_enabled) self.update_title_block() message = "Основная надпись включена" if self._title_block_enabled else "Основная надпись отключена" self.show_temporary_message(message, 2000) def update_navigation_buttons(self): viewer = self.get_current_pdf_viewer() if not viewer: total_pages = 0 current_page = 0 else: total_pages = viewer._total_pages current_page = viewer._current_page_index + 1 self.page_spin.blockSignals(True) self.page_spin.setMaximum(max(1, total_pages)) self.page_spin.setValue(current_page) self.page_spin.blockSignals(False) self.total_pages_label.setText(f"/ {total_pages}") self.first_page_btn.setEnabled(total_pages > 1 and current_page > 1) self.prev_page_btn.setEnabled(total_pages > 1 and current_page > 1) self.next_page_btn.setEnabled(total_pages > 1 and current_page < total_pages) self.last_page_btn.setEnabled(total_pages > 1 and current_page < total_pages) def on_page_spin_changed(self, page_num): if not self._is_loading: viewer = self.get_current_pdf_viewer() if viewer: viewer.goto_page(page_num) def update_page_navigation(self, current_page: int, total_pages: int): self.update_navigation_buttons() tab = self.get_current_tab() if tab and tab.file_path: self.setWindowTitle(f"PDF Viewer — {tab.file_name} (стр. {current_page}/{total_pages})") def create_shortcut(self, key_sequence, slot): shortcut = QKeySequence(key_sequence) action = self.addAction("") action.setShortcut(shortcut) action.triggered.connect(slot) return action def zoom_in_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.zoom_in() def zoom_out_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.zoom_out() def reset_zoom_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.reset_zoom() def fit_width_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.fit_width() def fit_window_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.fit_window() def rotate_clockwise_current(self): logger.debug("ViewerWindow: поворот по часовой стрелке") viewer = self.get_current_pdf_viewer() if viewer: viewer.rotate_clockwise() # Явно вызываем обновление основной надписи после поворота QTimer.singleShot(50, self.update_title_block) def rotate_counterclockwise_current(self): logger.debug("ViewerWindow: поворот против часовой стрелки") viewer = self.get_current_pdf_viewer() if viewer: viewer.rotate_counterclockwise() # Явно вызываем обновление основной надписи после поворота QTimer.singleShot(50, self.update_title_block) def next_page_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.next_page() def prev_page_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.prev_page() def first_page_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.goto_page(1) def last_page_current(self): viewer = self.get_current_pdf_viewer() if viewer: viewer.goto_page(viewer._total_pages) def open_pdf_dialog(self): last_dir = self.settings.value("last_directory", "") if not last_dir: tab = self.get_current_tab() if tab and tab.file_path: last_dir = os.path.dirname(tab.file_path) if not last_dir: last_dir = str(Path.home()) file_path, _ = QFileDialog.getOpenFileName( self, "Выберите PDF файл", last_dir, "PDF Files (*.pdf);;All Files (*)" ) if file_path: self.activate_window() tab = self.get_current_tab() if tab and not tab.file_path: self.load_pdf_in_tab(self.tab_widget.currentIndex(), file_path) else: self.create_new_tab(file_path) def load_pdf_in_tab(self, tab_index, path, restoring=False): if tab_index < 0 or tab_index >= self.tab_widget.count(): return self.activate_window() tab = self.tabs.get(tab_index) if not tab: return if self._is_loading: return logger.info(f"Запрос на загрузку PDF в вкладку {tab_index}: {path}") if not os.path.isfile(path): logger.error(f"Файл не существует: {path}") self.show_temporary_message(f"Ошибка: файл не существует", 3000) return if not self.poppler_path: QMessageBox.warning(self, "Ошибка", "Poppler (pdftoppm) не найден") logger.error("Poppler не найден — отмена загрузки") self.show_temporary_message("Ошибка: Poppler не найден", 3000) return # Не обновляем last_file при восстановлении вкладок if not restoring: self.settings.setValue("last_file", path) self.settings.setValue("last_directory", os.path.dirname(path)) tab.file_path = path tab.file_name = os.path.basename(path) self.tab_widget.setTabText(tab_index, tab.file_name) tab.pdf_viewer.set_current_file(path) self.show_temporary_message(f"Загрузка PDF в вкладку {tab_index + 1}...", 0) self._is_loading = True self._start_render(tab_index, path) def load_pdf(self, path): for index, tab in self.tabs.items(): if tab.file_path == path: self.tab_widget.setCurrentIndex(index) self.activate_window() return tab = self.get_current_tab() if tab and not tab.file_path: self.load_pdf_in_tab(self.tab_widget.currentIndex(), path) else: self.create_new_tab(path) @pyqtSlot(str) def load_pdf_from_ipc(self, path: str): self.activate_window() self.load_pdf(path) def _start_render(self, tab_index, path): logger.debug(f"Запуск рендеринга для вкладки {tab_index}: {path}") self.progress_dialog = QProgressDialog(f"Загрузка страниц (вкладка {tab_index + 1})...", "Отмена", 0, 100, self) self.progress_dialog.setWindowTitle("Загрузка PDF") self.progress_dialog.setWindowModality(Qt.WindowModality.WindowModal) self.progress_dialog.setAutoClose(True) thread = QThread() worker = render_worker.PDFRenderWorker() worker.moveToThread(thread) def on_finished(images, p, total_pages): worker.deleteLater() thread.quit() thread.wait() thread.deleteLater() tab = self.tabs.get(tab_index) if tab: tab.pdf_viewer.set_pdf_pages(images, total_pages) self._is_loading = False logger.info(f"PDF успешно отображён в вкладке {tab_index}: {p}, страниц: {total_pages}") # Не обновляем last_file при восстановлении вкладок if not self._restoring_tabs: self.settings.setValue("last_file", p) self.settings.setValue("last_directory", os.path.dirname(p)) if self.progress_dialog: self.progress_dialog.close() if self._current_dimensions: self._restore_page_parameters() self.update_title_block() def on_error(msg): worker.deleteLater() thread.quit() thread.wait() thread.deleteLater() self._is_loading = False logger.error(f"Ошибка рендеринга в вкладке {tab_index}: {msg}") QMessageBox.critical(self, "Ошибка", f"Не удалось открыть PDF:\n{msg}") self.show_temporary_message(f"Ошибка: {msg}", 5000) if not self._restoring_tabs: self.settings.remove("last_file") if self.progress_dialog: self.progress_dialog.close() def on_page_rendered(page_num, image): tab = self.tabs.get(tab_index) if tab: tab.pdf_viewer.add_page_to_cache(page_num, image) def on_progress(value): if self.progress_dialog: self.progress_dialog.setValue(value) def cancel_render(): thread.quit() thread.wait() self._is_loading = False self.show_temporary_message("Загрузка отменена", 3000) worker.finished.connect(on_finished) worker.error.connect(on_error) worker.page_rendered.connect(on_page_rendered) worker.progress.connect(on_progress) self.progress_dialog.canceled.connect(cancel_render) thread.started.connect(lambda: worker.render(path, self.poppler_path, self._cache_pages)) thread.start() self.progress_dialog.show() def open_settings(self): dialog = utils.SettingsDialog(self) if dialog.exec() == QDialog.DialogCode.Accepted: new_settings = dialog.get_settings() for key, value in new_settings.items(): self.settings.setValue(key, value) for tab in self.tabs.values(): tab.pdf_viewer._save_file_state = new_settings["save_file_state"] tab.pdf_viewer._cache_pages = new_settings["cache_pages"] self._cache_pages = new_settings["cache_pages"] self._title_block_enabled = new_settings["title_block_enabled"] self._title_block_scale = new_settings["title_block_scale"] self._limit_tabs = new_settings["limit_tabs"] self._max_tabs = new_settings["max_tabs"] self._close_first_tab = new_settings["close_first_tab"] self._restore_tabs = new_settings["restore_tabs"] # Новая настройка for tab in self.tabs.values(): tab.update_title_block_visibility(self._title_block_enabled) self.update_title_block() logger.info("Настройки сохранены") self.show_temporary_message("Настройки сохранены успешно!", 3000) def print_document(self): viewer = self.get_current_pdf_viewer() if not viewer or not hasattr(viewer, '_original_images') or not viewer._original_images: self.show_temporary_message("Нет загруженного PDF для печати", 3000) return printer = QPrinter(QPrinter.PrinterMode.HighResolution) print_dialog = QPrintDialog(printer, self) print_dialog.setWindowTitle("Печать PDF") print_dialog.setOptions( QPrintDialog.PrintDialogOption.PrintToFile | QPrintDialog.PrintDialogOption.PrintPageRange | QPrintDialog.PrintDialogOption.PrintSelection ) if print_dialog.exec() == QDialog.DialogCode.Accepted: try: painter = QPainter() painter.begin(printer) image = viewer.get_current_image_for_printing() if image.isNull(): painter.end() self.show_temporary_message("Не удалось получить изображение для печати", 3000) return page_rect = printer.pageRect(QPrinter.Unit.DevicePixel) image_rect = image.rect() scale = min(page_rect.width() / image_rect.width(), page_rect.height() / image_rect.height()) x = (page_rect.width() - image_rect.width() * scale) / 2 y = (page_rect.height() - image_rect.height() * scale) / 2 painter.translate(x, y) painter.scale(scale, scale) painter.drawImage(0, 0, image) painter.end() self.show_temporary_message("Документ отправлен на печать", 3000) logger.info(f"Страница {viewer._current_page_index + 1} отправлена на печать") except Exception as e: logger.error(f"Ошибка при печати: {e}") self.show_temporary_message(f"Ошибка печати: {e}", 3000) def print_preview(self): viewer = self.get_current_pdf_viewer() if not viewer or not hasattr(viewer, '_original_images') or not viewer._original_images: self.show_temporary_message("Нет загруженного PDF для предпросмотра", 3000) return printer = QPrinter(QPrinter.PrinterMode.HighResolution) preview_dialog = QPrintPreviewDialog(printer, self) preview_dialog.setWindowTitle("Предпросмотр печати") preview_dialog.paintRequested.connect(self.paint_printer) preview_dialog.exec() def paint_printer(self, printer): viewer = self.get_current_pdf_viewer() if not viewer: return painter = QPainter() painter.begin(printer) image = viewer.get_current_image_for_printing() if image.isNull(): painter.end() return page_rect = printer.pageRect(QPrinter.Unit.DevicePixel) image_rect = image.rect() scale = min(page_rect.width() / image_rect.width(), page_rect.height() / image_rect.height()) x = (page_rect.width() - image_rect.width() * scale) / 2 y = (page_rect.height() - image_rect.height() * scale) / 2 painter.translate(x, y) painter.scale(scale, scale) painter.drawImage(0, 0, image) painter.end() def show_temporary_message(self, message, timeout=3000): if timeout > 0: self.status_bar.showMessage(message, timeout) else: self.status_bar.showMessage(message) if timeout > 0 and self._current_dimensions: QTimer.singleShot(timeout, self._restore_page_parameters) def _restore_page_parameters(self): if self._current_dimensions: self._update_status_bar_display(self._current_dimensions) def _update_status_bar_display(self, dimensions: dict): status_text = ( f"Страница: {dimensions['current_page']}/{dimensions['total_pages']} | " f"DPI: {dimensions['dpi']} | " f"Пиксели: {dimensions['width_px']}×{dimensions['height_px']} | " f"мм: {dimensions['width_mm']}×{dimensions['height_mm']} | " f"Диагональ: {dimensions['diagonal_mm']} мм | " f"Формат: {dimensions['format']} | " f"Масштаб: {dimensions['zoom_percent']}%" ) self.status_bar.showMessage(status_text) @pyqtSlot(dict) def update_status_bar(self, dimensions: dict): self._current_dimensions = dimensions self._update_status_bar_display(dimensions) def show_pdf_size(self): viewer = self.get_current_pdf_viewer() if not viewer or not hasattr(viewer, '_original_images') or not viewer._original_images: self.show_temporary_message("Нет загруженного PDF", 3000) return if viewer._current_page_index >= len(viewer._original_images): return img = viewer._original_images[viewer._current_page_index] w_px = img.width() h_px = img.height() dpi = viewer._render_dpi w_mm, h_mm, d_mm, fmt = utils.calculate_dimensions(w_px, h_px, dpi) msg = ( f"Размеры страницы {viewer._current_page_index + 1}:\n" f"Пиксели: {w_px} × {h_px}\n" f"Миллиметры (DPI={dpi}): {w_mm} × {h_mm} мм\n" f"Диагональ: {d_mm} мм\n" f"Формат: {fmt}" ) QMessageBox.information(self, "Размеры PDF", msg) def resizeEvent(self, event): super().resizeEvent(event) tab = self.get_current_tab() if tab: tab.update_title_block_geometry() def closeEvent(self, event): # Сохраняем состояние всех вкладок for tab in self.tabs.values(): if tab.pdf_viewer: tab.pdf_viewer.save_state() # Сохраняем размер и положение окна save_window_state = self.settings.value("save_window_state", True, type=bool) if save_window_state: self.settings.setValue("window_size", self.size()) self.settings.setValue("window_position", self.pos()) # Сохраняем вкладки self.save_tabs_on_close() super().closeEvent(event)