Files
Browser/main.py

578 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()