578 lines
22 KiB
Python
578 lines
22 KiB
Python
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() |