From 1d6021f3046f3cd73d78dc55087be83fd821d498 Mon Sep 17 00:00:00 2001 From: Arkon Date: Sun, 11 Jan 2026 20:56:41 +0700 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 578 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 578 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..f93af8c --- /dev/null +++ b/main.py @@ -0,0 +1,578 @@ +import sys +import json +import os +from datetime import datetime +from PyQt6.QtCore import * +from PyQt6.QtWidgets import * +from PyQt6.QtWebEngineWidgets import * +from PyQt6.QtWebEngineCore import * +from PyQt6.QtGui import * + +class BrowserWindow(QMainWindow): + def __init__(self): + super().__init__() + + # Настройки поисковых систем + self.search_engines = { + "Google": "https://www.google.com/search?q={}", + "DuckDuckGo": "https://duckduckgo.com/?q={}", + "Bing": "https://www.bing.com/search?q={}", + "Yandex": "https://yandex.ru/search/?text={}" + } + self.current_search_engine = "Google" + + # Инициализация данных + self.bookmarks = [] + self.history = [] + self.load_data() + + self.setup_ui() + self.apply_dark_blue_theme() + + def setup_ui(self): + """Настройка пользовательского интерфейса""" + self.setWindowTitle("Dark Blue Browser") + self.setGeometry(100, 100, 1400, 800) + + # Центральный виджет + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # Основной макет + main_layout = QVBoxLayout(central_widget) + + # Панель навигации + nav_bar = QHBoxLayout() + + # Кнопка назад + self.back_btn = QPushButton("←") + self.back_btn.clicked.connect(self.navigate_back) + + # Кнопка вперед + self.forward_btn = QPushButton("→") + self.forward_btn.clicked.connect(self.navigate_forward) + + # Кнопка обновить + self.reload_btn = QPushButton("↻") + self.reload_btn.clicked.connect(self.reload_page) + + # Адресная строка + self.url_bar = QLineEdit() + self.url_bar.returnPressed.connect(self.navigate_to_url) + + # Кнопка перехода + self.go_btn = QPushButton("Go") + self.go_btn.clicked.connect(self.navigate_to_url) + + # Выбор поисковой системы + self.search_combo = QComboBox() + self.search_combo.addItems(self.search_engines.keys()) + self.search_combo.setCurrentText(self.current_search_engine) + self.search_combo.currentTextChanged.connect(self.change_search_engine) + + # Поле поиска + self.search_bar = QLineEdit() + self.search_bar.setPlaceholderText("Поиск...") + self.search_bar.returnPressed.connect(self.perform_search) + + # Кнопка закладок + self.bookmark_btn = QPushButton("⭐") + self.bookmark_btn.clicked.connect(self.toggle_bookmark) + self.bookmark_btn.setToolTip("Добавить/удалить закладку") + + # Кнопка истории + self.history_btn = QPushButton("📜") + self.history_btn.clicked.connect(self.show_history) + self.history_btn.setToolTip("Показать историю") + + # Добавление элементов в панель навигации + nav_bar.addWidget(self.back_btn) + nav_bar.addWidget(self.forward_btn) + nav_bar.addWidget(self.reload_btn) + nav_bar.addWidget(QLabel("URL:")) + nav_bar.addWidget(self.url_bar, 2) + nav_bar.addWidget(self.go_btn) + nav_bar.addWidget(QLabel("Поиск:")) + nav_bar.addWidget(self.search_bar, 1) + nav_bar.addWidget(self.search_combo) + nav_bar.addWidget(self.bookmark_btn) + nav_bar.addWidget(self.history_btn) + + # Веб-вью + self.browser = QWebEngineView() + self.browser.urlChanged.connect(self.update_url) + self.browser.loadFinished.connect(self.on_page_loaded) + + # Принудительное включение темного режима для всех сайтов + profile = QWebEngineProfile.defaultProfile() + profile.setHttpUserAgent(profile.httpUserAgent() + " DarkMode/1.0") + + # Добавление в макет + main_layout.addLayout(nav_bar) + main_layout.addWidget(self.browser) + + # Загрузка домашней страницы + self.load_home_page() + + def apply_dark_blue_theme(self): + """Применение темно-голубой темы""" + dark_blue_theme = """ + QMainWindow { + background-color: #0f1c2e; + } + QWidget { + background-color: #0f1c2e; + color: #e0f0ff; + font-family: 'Segoe UI', Arial, sans-serif; + font-size: 12px; + } + QPushButton { + background-color: #1e3a5f; + color: #e0f0ff; + border: 1px solid #2d4a7a; + border-radius: 4px; + padding: 5px 12px; + font-weight: bold; + } + QPushButton:hover { + background-color: #2d4a7a; + border-color: #3d5a9a; + } + QPushButton:pressed { + background-color: #1a3257; + } + QLineEdit { + background-color: #1a2b44; + color: #ffffff; + border: 1px solid #2d4a7a; + border-radius: 4px; + padding: 5px; + selection-background-color: #2d4a7a; + } + QLineEdit:focus { + border: 1px solid #4a7ac9; + } + QComboBox { + background-color: #1a2b44; + color: #ffffff; + border: 1px solid #2d4a7a; + border-radius: 4px; + padding: 5px; + min-width: 100px; + } + QComboBox::drop-down { + border: none; + } + QComboBox QAbstractItemView { + background-color: #1a2b44; + color: #ffffff; + selection-background-color: #2d4a7a; + } + QLabel { + color: #a0c8ff; + font-weight: bold; + } + QWebEngineView { + background-color: #0a1525; + border: 1px solid #1e3a5f; + border-radius: 4px; + } + QMenu { + background-color: #1a2b44; + color: #ffffff; + border: 1px solid #2d4a7a; + } + QMenu::item:selected { + background-color: #2d4a7a; + } + """ + self.setStyleSheet(dark_blue_theme) + + # Также применяем темную тему для веб-контента + settings = self.browser.settings() + settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True) + + def load_home_page(self): + """Загрузка домашней страницы""" + self.browser.setUrl(QUrl("https://www.google.com")) + + def navigate_to_url(self): + """Переход по URL""" + url = self.url_bar.text() + + # Проверка, является ли ввод поисковым запросом + if not url.startswith(('http://', 'https://')): + if ' ' in url or '.' not in url: + # Это поисковый запрос + self.perform_search_with_query(url) + return + else: + url = 'https://' + url + + self.browser.setUrl(QUrl(url)) + + def perform_search(self): + """Выполнение поиска""" + query = self.search_bar.text() + if query: + self.perform_search_with_query(query) + + def perform_search_with_query(self, query): + """Выполнение поиска с заданным запросом""" + search_url = self.search_engines[self.current_search_engine].format(query) + self.browser.setUrl(QUrl(search_url)) + + # Добавление в историю поиска + self.add_to_history(query, search_url) + + def change_search_engine(self, engine): + """Изменение поисковой системы""" + self.current_search_engine = engine + + def navigate_back(self): + """Навигация назад""" + self.browser.back() + + def navigate_forward(self): + """Навигация вперед""" + self.browser.forward() + + def reload_page(self): + """Обновление страницы""" + self.browser.reload() + + def update_url(self, q): + """Обновление URL в адресной строке""" + self.url_bar.setText(q.toString()) + + # Добавление в историю посещений + if q.toString() not in [h['url'] for h in self.history[-20:]]: + self.add_to_history(self.browser.page().title(), q.toString()) + + def on_page_loaded(self, ok): + """Действия после загрузки страницы""" + if ok: + current_url = self.browser.url().toString() + self.url_bar.setText(current_url) + + # Применение темного режима к странице через JavaScript + dark_mode_js = """ + // Создаем стиль для темного режима + let darkModeStyle = document.createElement('style'); + darkModeStyle.id = 'dark-mode-style'; + darkModeStyle.textContent = ` + body { + background-color: #0a1525 !important; + color: #e0f0ff !important; + } + * { + background-color: rgba(10, 21, 37, 0.9) !important; + color: #e0f0ff !important; + border-color: #2d4a7a !important; + } + input, textarea, select { + background-color: #1a2b44 !important; + color: #ffffff !important; + } + a { + color: #4a7ac9 !important; + } + a:visited { + color: #8a5ac9 !important; + } + button, .btn { + background-color: #1e3a5f !important; + color: #e0f0ff !important; + border-color: #2d4a7a !important; + } + `; + + // Удаляем старый стиль если есть + let oldStyle = document.getElementById('dark-mode-style'); + if (oldStyle) oldStyle.remove(); + + // Добавляем новый стиль + document.head.appendChild(darkModeStyle); + + // Дополнительные настройки для популярных сайтов + if (window.location.hostname.includes('google.com')) { + // Для Google + document.body.style.backgroundColor = '#0a1525'; + document.body.style.color = '#e0f0ff'; + } + if (window.location.hostname.includes('youtube.com')) { + // Для YouTube + document.body.style.backgroundColor = '#0a1525'; + } + """ + + # Внедряем JavaScript для темного режима + self.browser.page().runJavaScript(dark_mode_js) + + def toggle_bookmark(self): + """Добавление/удаление закладки""" + current_url = self.browser.url().toString() + page_title = self.browser.page().title() + + # Проверяем, есть ли уже в закладках + existing_index = -1 + for i, bookmark in enumerate(self.bookmarks): + if bookmark['url'] == current_url: + existing_index = i + break + + if existing_index >= 0: + # Удаляем закладку + del self.bookmarks[existing_index] + QMessageBox.information(self, "Закладка", "Закладка удалена!") + else: + # Добавляем закладку + self.bookmarks.append({ + 'title': page_title, + 'url': current_url, + 'date': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + }) + QMessageBox.information(self, "Закладка", "Страница добавлена в закладки!") + + self.save_data() + + def show_history(self): + """Показ истории поиска""" + history_dialog = QDialog(self) + history_dialog.setWindowTitle("История просмотров") + history_dialog.setGeometry(200, 200, 600, 400) + + layout = QVBoxLayout() + + # Таблица истории + table = QTableWidget() + table.setColumnCount(3) + table.setHorizontalHeaderLabels(["Дата", "Заголовок", "URL"]) + + table.setRowCount(len(self.history)) + for i, item in enumerate(reversed(self.history[-50:])): # Последние 50 записей + table.setItem(i, 0, QTableWidgetItem(item['date'])) + table.setItem(i, 1, QTableWidgetItem(item['title'][:50] + "..." if len(item['title']) > 50 else item['title'])) + table.setItem(i, 2, QTableWidgetItem(item['url'][:100] + "..." if len(item['url']) > 100 else item['url'])) + + table.resizeColumnsToContents() + + # Кнопки + button_box = QDialogButtonBox() + clear_btn = QPushButton("Очистить историю") + close_btn = QPushButton("Закрыть") + + clear_btn.clicked.connect(lambda: self.clear_history(table)) + close_btn.clicked.connect(history_dialog.close) + + button_box.addButton(clear_btn, QDialogButtonBox.ButtonRole.ActionRole) + button_box.addButton(close_btn, QDialogButtonBox.ButtonRole.RejectRole) + + layout.addWidget(table) + layout.addWidget(button_box) + + history_dialog.setLayout(layout) + history_dialog.exec() + + def add_to_history(self, title, url): + """Добавление записи в историю""" + self.history.append({ + 'title': title, + 'url': url, + 'date': datetime.now().strftime("%Y-%m-%d %H:%M:%S") + }) + self.save_data() + + def clear_history(self, table): + """Очистка истории""" + reply = QMessageBox.question(self, "Очистка истории", + "Вы уверены, что хотите очистить всю историю?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + + if reply == QMessageBox.StandardButton.Yes: + self.history.clear() + table.setRowCount(0) + self.save_data() + + def save_data(self): + """Сохранение данных в файлы""" + data_dir = "browser_data" + if not os.path.exists(data_dir): + os.makedirs(data_dir) + + with open(os.path.join(data_dir, "bookmarks.json"), "w", encoding="utf-8") as f: + json.dump(self.bookmarks, f, ensure_ascii=False, indent=2) + + with open(os.path.join(data_dir, "history.json"), "w", encoding="utf-8") as f: + json.dump(self.history, f, ensure_ascii=False, indent=2) + + def load_data(self): + """Загрузка данных из файлов""" + data_dir = "browser_data" + + # Загрузка закладок + bookmarks_file = os.path.join(data_dir, "bookmarks.json") + if os.path.exists(bookmarks_file): + with open(bookmarks_file, "r", encoding="utf-8") as f: + self.bookmarks = json.load(f) + + # Загрузка истории + history_file = os.path.join(data_dir, "history.json") + if os.path.exists(history_file): + with open(history_file, "r", encoding="utf-8") as f: + self.history = json.load(f) + + def contextMenuEvent(self, event): + """Контекстное меню""" + menu = QMenu(self) + + # Добавление действий + back_action = menu.addAction("Назад") + forward_action = menu.addAction("Вперед") + reload_action = menu.addAction("Обновить") + menu.addSeparator() + + bookmark_action = menu.addAction("Добавить в закладки") + show_bookmarks_action = menu.addAction("Показать закладки") + menu.addSeparator() + + history_action = menu.addAction("Показать историю") + settings_action = menu.addAction("Настройки") + + # Обработка выбора + action = menu.exec(self.mapToGlobal(event.pos())) + + if action == back_action: + self.navigate_back() + elif action == forward_action: + self.navigate_forward() + elif action == reload_action: + self.reload_page() + elif action == bookmark_action: + self.toggle_bookmark() + elif action == show_bookmarks_action: + self.show_bookmarks() + elif action == history_action: + self.show_history() + elif action == settings_action: + self.show_settings() + + def show_bookmarks(self): + """Показ закладок""" + bookmarks_dialog = QDialog(self) + bookmarks_dialog.setWindowTitle("Закладки") + bookmarks_dialog.setGeometry(200, 200, 600, 400) + + layout = QVBoxLayout() + + # Список закладок + list_widget = QListWidget() + for bookmark in self.bookmarks: + item_text = f"{bookmark['title']} - {bookmark['date']}" + item = QListWidgetItem(item_text) + item.setData(Qt.ItemDataRole.UserRole, bookmark['url']) + list_widget.addItem(item) + + list_widget.itemDoubleClicked.connect( + lambda item: self.browser.setUrl(QUrl(item.data(Qt.ItemDataRole.UserRole))) + ) + + # Кнопки + button_box = QDialogButtonBox() + open_btn = QPushButton("Открыть") + delete_btn = QPushButton("Удалить") + close_btn = QPushButton("Закрыть") + + open_btn.clicked.connect( + lambda: self.open_bookmark(list_widget.currentItem()) + ) + delete_btn.clicked.connect( + lambda: self.delete_bookmark(list_widget) + ) + close_btn.clicked.connect(bookmarks_dialog.close) + + button_box.addButton(open_btn, QDialogButtonBox.ButtonRole.ActionRole) + button_box.addButton(delete_btn, QDialogButtonBox.ButtonRole.ActionRole) + button_box.addButton(close_btn, QDialogButtonBox.ButtonRole.RejectRole) + + layout.addWidget(list_widget) + layout.addWidget(button_box) + + bookmarks_dialog.setLayout(layout) + bookmarks_dialog.exec() + + def open_bookmark(self, item): + """Открытие закладки""" + if item: + url = item.data(Qt.ItemDataRole.UserRole) + self.browser.setUrl(QUrl(url)) + + def delete_bookmark(self, list_widget): + """Удаление закладки""" + current_item = list_widget.currentItem() + if current_item: + current_row = list_widget.row(current_item) + if 0 <= current_row < len(self.bookmarks): + del self.bookmarks[current_row] + list_widget.takeItem(current_row) + self.save_data() + + def show_settings(self): + """Показ настроек""" + settings_dialog = QDialog(self) + settings_dialog.setWindowTitle("Настройки браузера") + settings_dialog.setGeometry(300, 300, 400, 300) + + layout = QVBoxLayout() + + # Настройки поиска + search_group = QGroupBox("Настройки поиска") + search_layout = QVBoxLayout() + + search_label = QLabel("Поисковая система по умолчанию:") + self.settings_search_combo = QComboBox() + self.settings_search_combo.addItems(self.search_engines.keys()) + self.settings_search_combo.setCurrentText(self.current_search_engine) + + search_layout.addWidget(search_label) + search_layout.addWidget(self.settings_search_combo) + search_group.setLayout(search_layout) + + # Кнопки + button_box = QDialogButtonBox() + save_btn = QPushButton("Сохранить") + cancel_btn = QPushButton("Отмена") + + save_btn.clicked.connect( + lambda: self.save_settings(settings_dialog) + ) + cancel_btn.clicked.connect(settings_dialog.close) + + button_box.addButton(save_btn, QDialogButtonBox.ButtonRole.AcceptRole) + button_box.addButton(cancel_btn, QDialogButtonBox.ButtonRole.RejectRole) + + layout.addWidget(search_group) + layout.addWidget(button_box) + + settings_dialog.setLayout(layout) + settings_dialog.exec() + + def save_settings(self, dialog): + """Сохранение настроек""" + self.current_search_engine = self.settings_search_combo.currentText() + self.search_combo.setCurrentText(self.current_search_engine) + dialog.close() + QMessageBox.information(self, "Настройки", "Настройки сохранены!") + +def main(): + app = QApplication(sys.argv) + app.setApplicationName("Dark Blue Browser") + app.setStyle("Fusion") + + window = BrowserWindow() + window.show() + + sys.exit(app.exec()) + +if __name__ == "__main__": + main() \ No newline at end of file