MoneyPack-Security-Suite / moneypack_browser.py
MoneyPack's picture
MoneyPack Browser v1.0 - Custom web browser with ad blocker, speed dial, dark theme
2e1500f verified
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MoneyPack Browser v1.0
Premium Web Browser - Better than Opera/Vivaldi
Created by MoneyPack
Features:
- Tabbed browsing with animated tab bar
- Custom dark luxury UI
- Built-in ad blocker
- Sidebar (bookmarks, history, downloads)
- Speed dial new tab page with MoneyPack branding
- Download manager
- Private/Incognito mode
- Custom frameless window
- Keyboard shortcuts
- Find in page
- Zoom controls
- Print support
- Developer tools
- Full screen mode
"""
import sys
import os
import json
import time
from pathlib import Path
from datetime import datetime
from urllib.parse import urlparse
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import (
QWebEnginePage, QWebEngineProfile, QWebEngineSettings,
QWebEngineUrlRequestInterceptor
)
# ═══════════════════════════════════════════════════════
# CONFIG
# ═══════════════════════════════════════════════════════
APP_NAME = "MoneyPack Browser"
APP_VERSION = "1.0.0"
DATA_DIR = Path.home() / ".moneypack_browser"
BOOKMARKS_FILE = DATA_DIR / "bookmarks.json"
HISTORY_FILE = DATA_DIR / "history.json"
SETTINGS_FILE = DATA_DIR / "settings.json"
# Ensure data dir
DATA_DIR.mkdir(parents=True, exist_ok=True)
# Colors
class C:
BG = "#08080f"
BG2 = "#0e0e1a"
PANEL = "#12122a"
CARD = "#1a1a38"
HOVER = "#222250"
TAB = "#161630"
TAB_ACTIVE = "#1e1e42"
GOLD = "#d4af37"
PINK = "#ff1a5c"
CYAN = "#00e5ff"
PURPLE = "#a855f7"
WHITE = "#f0f0ff"
TEXT = "#c0c0dd"
DIM = "#5a5a80"
MUTED = "#2a2a4a"
GREEN = "#00e676"
RED = "#ff1744"
BORDER = "#ffffff10"
URL_BG = "#0c0c1e"
# ═══════════════════════════════════════════════════════
# AD BLOCKER
# ═══════════════════════════════════════════════════════
AD_DOMAINS = {
"doubleclick.net", "googlesyndication.com", "googleadservices.com",
"adservice.google.com", "pagead2.googlesyndication.com",
"facebook.com/tr", "analytics.google.com",
"ad.doubleclick.net", "ads.yahoo.com", "ads.twitter.com",
"advertising.com", "adnxs.com", "adsrvr.org",
"outbrain.com", "taboola.com", "criteo.com", "criteo.net",
"moatads.com", "quantserve.com", "scorecardresearch.com",
"amazon-adsystem.com", "adobedtm.com",
"hotjar.com", "mouseflow.com", "fullstory.com",
"popads.net", "popcash.net", "propellerads.com",
"adcolony.com", "applovin.com", "unity3d.com/ads",
"chartbeat.com", "optimizely.com", "crazyegg.com",
}
class AdBlocker(QWebEngineUrlRequestInterceptor):
"""Block ads and trackers."""
def __init__(self):
super().__init__()
self.blocked_count = 0
def interceptRequest(self, info):
url = info.requestUrl().toString()
host = info.requestUrl().host()
# Check against ad domains
for ad_domain in AD_DOMAINS:
if ad_domain in host:
info.block(True)
self.blocked_count += 1
return
# ═══════════════════════════════════════════════════════
# DATA MANAGEMENT
# ═══════════════════════════════════════════════════════
class DataManager:
"""Manage bookmarks, history, settings."""
@staticmethod
def load_json(path, default=None):
if default is None:
default = []
try:
if path.exists():
return json.loads(path.read_text())
except:
pass
return default
@staticmethod
def save_json(path, data):
try:
path.write_text(json.dumps(data, indent=2))
except:
pass
@classmethod
def get_bookmarks(cls):
return cls.load_json(BOOKMARKS_FILE, [
{"title": "Google", "url": "https://www.google.com"},
{"title": "YouTube", "url": "https://www.youtube.com"},
{"title": "GitHub", "url": "https://github.com"},
{"title": "Reddit", "url": "https://www.reddit.com"},
])
@classmethod
def save_bookmarks(cls, bookmarks):
cls.save_json(BOOKMARKS_FILE, bookmarks)
@classmethod
def add_bookmark(cls, title, url):
bm = cls.get_bookmarks()
bm.append({"title": title, "url": url, "added": datetime.now().isoformat()})
cls.save_bookmarks(bm)
@classmethod
def get_history(cls):
return cls.load_json(HISTORY_FILE, [])
@classmethod
def add_history(cls, title, url):
h = cls.get_history()
h.insert(0, {"title": title, "url": url, "time": datetime.now().isoformat()})
h = h[:1000] # Keep last 1000
cls.save_json(HISTORY_FILE, h)
@classmethod
def clear_history(cls):
cls.save_json(HISTORY_FILE, [])
# ═══════════════════════════════════════════════════════
# CUSTOM TAB BAR
# ═══════════════════════════════════════════════════════
class BrowserTab(QWidget):
"""Single browser tab with web view."""
def __init__(self, url="", private=False):
super().__init__()
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
if private:
profile = QWebEngineProfile()
self.page = QWebEnginePage(profile)
self.web = QWebEngineView()
self.web.setPage(self.page)
else:
self.web = QWebEngineView()
# Settings
settings = self.web.settings()
settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.FullScreenSupportEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.ScrollAnimatorEnabled, True)
layout.addWidget(self.web)
if url:
self.web.setUrl(QUrl(url))
def navigate(self, url):
if not url.startswith(("http://", "https://", "file://", "about:")):
if "." in url and " " not in url:
url = "https://" + url
else:
url = f"https://www.google.com/search?q={url}"
self.web.setUrl(QUrl(url))
# ═══════════════════════════════════════════════════════
# NEW TAB PAGE (Speed Dial)
# ═══════════════════════════════════════════════════════
NEW_TAB_HTML = """
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #08080f;
color: #f0f0ff;
font-family: 'Segoe UI', sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.particles {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: radial-gradient(ellipse at center, #12122a 0%, #08080f 70%);
z-index: 0;
}
.content { position: relative; z-index: 1; text-align: center; width: 90%; max-width: 800px; }
.logo {
font-size: 42px; font-weight: 900; margin-bottom: 8px;
background: linear-gradient(135deg, #ff1a5c, #d4af37);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
text-shadow: 0 0 40px rgba(255,26,92,0.3);
animation: glow 3s ease-in-out infinite alternate;
}
@keyframes glow {
from { filter: brightness(1); }
to { filter: brightness(1.2); }
}
.subtitle { color: #5a5a80; font-size: 14px; margin-bottom: 40px; letter-spacing: 2px; }
.search-box {
width: 100%; max-width: 600px; margin: 0 auto 50px;
position: relative;
}
.search-box input {
width: 100%; padding: 16px 24px; font-size: 16px;
background: #12122a; border: 1px solid #2a2a4a;
border-radius: 30px; color: #f0f0ff; outline: none;
transition: all 0.3s ease;
}
.search-box input:focus {
border-color: #ff1a5c;
box-shadow: 0 0 20px rgba(255,26,92,0.2);
}
.speed-dial {
display: grid; grid-template-columns: repeat(4, 1fr);
gap: 16px; width: 100%; max-width: 700px; margin: 0 auto;
}
.dial-item {
background: #12122a; border: 1px solid #2a2a4a;
border-radius: 12px; padding: 20px 12px;
text-decoration: none; color: #c0c0dd;
transition: all 0.3s ease; cursor: pointer;
display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.dial-item:hover {
border-color: #ff1a5c; transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(255,26,92,0.15);
background: #1a1a38;
}
.dial-icon {
width: 40px; height: 40px; border-radius: 10px;
background: linear-gradient(135deg, #ff1a5c33, #a855f733);
display: flex; align-items: center; justify-content: center;
font-size: 20px;
}
.dial-title { font-size: 12px; font-weight: 600; }
.footer { position: fixed; bottom: 20px; color: #2a2a4a; font-size: 11px; }
</style>
</head>
<body>
<div class="particles"></div>
<div class="content">
<div class="logo">MoneyPack</div>
<div class="subtitle">B R O W S E R</div>
<div class="search-box">
<input type="text" placeholder="Search or enter URL..." id="search"
onkeydown="if(event.key==='Enter'){let v=this.value;if(!v.startsWith('http')&&v.includes('.')&&!v.includes(' ')){v='https://'+v}else if(!v.startsWith('http')){v='https://www.google.com/search?q='+encodeURIComponent(v)}window.location.href=v}">
</div>
<div class="speed-dial">
<a class="dial-item" href="https://www.google.com"><div class="dial-icon">G</div><div class="dial-title">Google</div></a>
<a class="dial-item" href="https://www.youtube.com"><div class="dial-icon">Y</div><div class="dial-title">YouTube</div></a>
<a class="dial-item" href="https://github.com"><div class="dial-icon">H</div><div class="dial-title">GitHub</div></a>
<a class="dial-item" href="https://www.reddit.com"><div class="dial-icon">R</div><div class="dial-title">Reddit</div></a>
<a class="dial-item" href="https://twitter.com"><div class="dial-icon">X</div><div class="dial-title">Twitter</div></a>
<a class="dial-item" href="https://www.twitch.tv"><div class="dial-icon">T</div><div class="dial-title">Twitch</div></a>
<a class="dial-item" href="https://discord.com"><div class="dial-icon">D</div><div class="dial-title">Discord</div></a>
<a class="dial-item" href="https://www.netflix.com"><div class="dial-icon">N</div><div class="dial-title">Netflix</div></a>
</div>
</div>
<div class="footer">MoneyPack Browser v1.0 | Created by MoneyPack</div>
<script>document.getElementById('search').focus();</script>
</body>
</html>
"""
# ═══════════════════════════════════════════════════════
# MAIN BROWSER WINDOW
# ═══════════════════════════════════════════════════════
class MoneyPackBrowser(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle(APP_NAME)
self.setMinimumSize(1200, 750)
self.resize(1400, 850)
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
self.setStyleSheet(f"QMainWindow {{ background: {C.BG}; }}")
# Ad blocker
self.ad_blocker = AdBlocker()
QWebEngineProfile.defaultProfile().setUrlRequestInterceptor(self.ad_blocker)
# Build UI
self._tabs = []
self._private_mode = False
self._build_ui()
self._setup_shortcuts()
# Open new tab
self.new_tab()
def _build_ui(self):
central = QWidget()
self.setCentralWidget(central)
layout = QVBoxLayout(central)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# ─── TITLE BAR ───
titlebar = QWidget()
titlebar.setFixedHeight(36)
titlebar.setStyleSheet(f"background: {C.BG};")
self._titlebar = titlebar
self._drag_pos = None
titlebar.mousePressEvent = self._title_press
titlebar.mouseMoveEvent = self._title_move
titlebar.mouseReleaseEvent = self._title_release
titlebar.mouseDoubleClickEvent = lambda e: self.showNormal() if self.isMaximized() else self.showMaximized()
tb_layout = QHBoxLayout(titlebar)
tb_layout.setContentsMargins(10, 0, 6, 0)
# Brand
brand = QLabel("MoneyPack")
brand.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
brand.setStyleSheet(f"color: {C.GOLD};")
tb_layout.addWidget(brand)
tb_layout.addStretch()
# Window controls
for txt, act in [("\u2014", self.showMinimized), ("\u25a1", lambda: self.showNormal() if self.isMaximized() else self.showMaximized()), ("\u2715", self.close)]:
b = QPushButton(txt)
b.setFixedSize(36, 28)
color = C.RED if txt == "\u2715" else C.DIM
b.setStyleSheet(f"QPushButton{{background:transparent;color:{color};border:none;font-size:12px;border-radius:4px;}}QPushButton:hover{{background:{C.HOVER};color:{C.WHITE};}}")
b.clicked.connect(act)
tb_layout.addWidget(b)
layout.addWidget(titlebar)
# ─── TAB BAR ───
tab_bar_widget = QWidget()
tab_bar_widget.setFixedHeight(38)
tab_bar_widget.setStyleSheet(f"background: {C.BG2};")
self._tab_bar_layout = QHBoxLayout(tab_bar_widget)
self._tab_bar_layout.setContentsMargins(8, 4, 8, 0)
self._tab_bar_layout.setSpacing(2)
self._tab_bar_layout.addStretch()
# New tab button
new_btn = QPushButton("+")
new_btn.setFixedSize(28, 28)
new_btn.setStyleSheet(f"QPushButton{{background:{C.CARD};color:{C.TEXT};border:none;border-radius:14px;font-size:16px;}}QPushButton:hover{{background:{C.PINK};color:white;}}")
new_btn.setCursor(Qt.CursorShape.PointingHandCursor)
new_btn.clicked.connect(self.new_tab)
self._tab_bar_layout.addWidget(new_btn)
layout.addWidget(tab_bar_widget)
# ─── TOOLBAR ───
toolbar = QWidget()
toolbar.setFixedHeight(44)
toolbar.setStyleSheet(f"background: {C.PANEL}; border-bottom: 1px solid {C.MUTED};")
tool_layout = QHBoxLayout(toolbar)
tool_layout.setContentsMargins(8, 4, 8, 4)
tool_layout.setSpacing(4)
# Nav buttons
btn_style = f"QPushButton{{background:transparent;color:{C.DIM};border:none;font-size:16px;padding:4px 8px;border-radius:6px;}}QPushButton:hover{{background:{C.HOVER};color:{C.WHITE};}}"
self._back_btn = QPushButton("\u2190")
self._back_btn.setStyleSheet(btn_style)
self._back_btn.clicked.connect(self._go_back)
tool_layout.addWidget(self._back_btn)
self._fwd_btn = QPushButton("\u2192")
self._fwd_btn.setStyleSheet(btn_style)
self._fwd_btn.clicked.connect(self._go_forward)
tool_layout.addWidget(self._fwd_btn)
self._reload_btn = QPushButton("\u21bb")
self._reload_btn.setStyleSheet(btn_style)
self._reload_btn.clicked.connect(self._reload)
tool_layout.addWidget(self._reload_btn)
self._home_btn = QPushButton("\u2302")
self._home_btn.setStyleSheet(btn_style)
self._home_btn.clicked.connect(self.new_tab)
tool_layout.addWidget(self._home_btn)
# URL bar
self._url_bar = QLineEdit()
self._url_bar.setPlaceholderText("Search or enter URL...")
self._url_bar.setStyleSheet(f"""
QLineEdit {{
background: {C.URL_BG};
border: 1px solid {C.MUTED};
border-radius: 20px;
padding: 8px 20px;
color: {C.WHITE};
font-size: 13px;
selection-background-color: {C.PINK}44;
}}
QLineEdit:focus {{
border-color: {C.PINK};
}}
""")
self._url_bar.returnPressed.connect(self._navigate_url)
tool_layout.addWidget(self._url_bar)
# Right buttons
self._bookmark_btn = QPushButton("\u2606")
self._bookmark_btn.setStyleSheet(btn_style)
self._bookmark_btn.clicked.connect(self._add_bookmark)
self._bookmark_btn.setToolTip("Bookmark this page")
tool_layout.addWidget(self._bookmark_btn)
self._private_btn = QPushButton("\u229a")
self._private_btn.setStyleSheet(btn_style)
self._private_btn.clicked.connect(self._toggle_private)
self._private_btn.setToolTip("Private Mode")
tool_layout.addWidget(self._private_btn)
# Ad block counter
self._adblock_label = QLabel("ADS: 0")
self._adblock_label.setStyleSheet(f"color: {C.GREEN}; font-size: 9px; font-weight: bold; padding: 0 8px;")
tool_layout.addWidget(self._adblock_label)
# Menu button
menu_btn = QPushButton("\u2261")
menu_btn.setStyleSheet(f"QPushButton{{background:transparent;color:{C.TEXT};border:none;font-size:20px;padding:4px 10px;border-radius:6px;}}QPushButton:hover{{background:{C.HOVER};}}")
menu_btn.clicked.connect(self._show_menu)
tool_layout.addWidget(menu_btn)
layout.addWidget(toolbar)
# ─── CONTENT ───
self._stack = QStackedWidget()
self._stack.setStyleSheet(f"background: {C.BG};")
layout.addWidget(self._stack)
# ─── STATUS BAR ───
status = QWidget()
status.setFixedHeight(22)
status.setStyleSheet(f"background: {C.BG2}; border-top: 1px solid {C.MUTED};")
sl = QHBoxLayout(status)
sl.setContentsMargins(10, 0, 10, 0)
self._status_label = QLabel("Ready")
self._status_label.setStyleSheet(f"color: {C.DIM}; font-size: 10px;")
sl.addWidget(self._status_label)
sl.addStretch()
self._zoom_label = QLabel("100%")
self._zoom_label.setStyleSheet(f"color: {C.DIM}; font-size: 10px;")
sl.addWidget(self._zoom_label)
layout.addWidget(status)
# Update ad counter timer
self._ad_timer = QTimer(self)
self._ad_timer.timeout.connect(self._update_adblock)
self._ad_timer.start(2000)
# ─── TAB MANAGEMENT ───
def new_tab(self, url=""):
tab = BrowserTab(private=self._private_mode)
if url:
tab.navigate(url)
else:
# Speed dial page
tab.web.setHtml(NEW_TAB_HTML)
# Connect signals
tab.web.titleChanged.connect(lambda title: self._update_tab_title(tab, title))
tab.web.urlChanged.connect(lambda qurl: self._on_url_changed(tab, qurl))
tab.web.loadStarted.connect(lambda: self._status_label.setText("Loading..."))
tab.web.loadFinished.connect(lambda ok: self._status_label.setText("Done" if ok else "Failed"))
self._tabs.append(tab)
self._stack.addWidget(tab)
self._stack.setCurrentWidget(tab)
self._rebuild_tab_bar()
self._url_bar.clear()
self._url_bar.setFocus()
def close_tab(self, index):
if len(self._tabs) <= 1:
self.close()
return
tab = self._tabs.pop(index)
self._stack.removeWidget(tab)
tab.deleteLater()
if index >= len(self._tabs):
index = len(self._tabs) - 1
self._stack.setCurrentWidget(self._tabs[index])
self._rebuild_tab_bar()
def switch_tab(self, index):
if 0 <= index < len(self._tabs):
self._stack.setCurrentWidget(self._tabs[index])
self._rebuild_tab_bar()
url = self._tabs[index].web.url().toString()
if url and not url.startswith("about:"):
self._url_bar.setText(url)
def _rebuild_tab_bar(self):
# Clear existing tab buttons (keep stretch and + button)
while self._tab_bar_layout.count() > 2:
item = self._tab_bar_layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
current_idx = self._tabs.index(self._stack.currentWidget()) if self._stack.currentWidget() in self._tabs else 0
for i, tab in enumerate(self._tabs):
title = tab.web.title() or "New Tab"
if len(title) > 20:
title = title[:18] + "..."
tab_widget = QWidget()
tab_widget.setFixedHeight(30)
tab_widget.setCursor(Qt.CursorShape.PointingHandCursor)
is_active = (i == current_idx)
bg = C.TAB_ACTIVE if is_active else C.TAB
border = C.PINK if is_active else "transparent"
tab_widget.setStyleSheet(f"background: {bg}; border: 1px solid {border}; border-bottom: none; border-radius: 8px 8px 0 0;")
tl = QHBoxLayout(tab_widget)
tl.setContentsMargins(12, 2, 4, 2)
tl.setSpacing(4)
# Private icon
if self._private_mode:
priv = QLabel("\u229a")
priv.setStyleSheet(f"color: {C.PURPLE}; font-size: 10px; border: none;")
tl.addWidget(priv)
title_lbl = QLabel(title)
title_lbl.setStyleSheet(f"color: {C.WHITE if is_active else C.DIM}; font-size: 11px; border: none; font-weight: {'bold' if is_active else 'normal'};")
tl.addWidget(title_lbl)
close_btn = QPushButton("\u2715")
close_btn.setFixedSize(18, 18)
close_btn.setStyleSheet(f"QPushButton{{background:transparent;color:{C.DIM};border:none;font-size:10px;border-radius:9px;}}QPushButton:hover{{background:{C.RED};color:white;}}")
close_btn.clicked.connect(lambda _, idx=i: self.close_tab(idx))
tl.addWidget(close_btn)
# Click to switch
tab_widget.mousePressEvent = lambda e, idx=i: self.switch_tab(idx)
self._tab_bar_layout.insertWidget(i, tab_widget)
def _update_tab_title(self, tab, title):
self._rebuild_tab_bar()
if tab == self._stack.currentWidget():
self.setWindowTitle(f"{title} - {APP_NAME}")
def _on_url_changed(self, tab, qurl):
if tab == self._stack.currentWidget():
url = qurl.toString()
if not url.startswith("about:") and not url.startswith("data:"):
self._url_bar.setText(url)
# Add to history
title = tab.web.title() or url
DataManager.add_history(title, url)
# ─── NAVIGATION ───
def _navigate_url(self):
url = self._url_bar.text().strip()
if not url:
return
current = self._stack.currentWidget()
if current and hasattr(current, 'navigate'):
current.navigate(url)
def _go_back(self):
current = self._stack.currentWidget()
if current:
current.web.back()
def _go_forward(self):
current = self._stack.currentWidget()
if current:
current.web.forward()
def _reload(self):
current = self._stack.currentWidget()
if current:
current.web.reload()
# ─── FEATURES ───
def _add_bookmark(self):
current = self._stack.currentWidget()
if current:
title = current.web.title()
url = current.web.url().toString()
if url and not url.startswith("about:"):
DataManager.add_bookmark(title, url)
self._bookmark_btn.setText("\u2605")
QTimer.singleShot(2000, lambda: self._bookmark_btn.setText("\u2606"))
self._status_label.setText(f"Bookmarked: {title}")
def _toggle_private(self):
self._private_mode = not self._private_mode
color = C.PURPLE if self._private_mode else C.DIM
self._private_btn.setStyleSheet(f"QPushButton{{background:{'#a855f733' if self._private_mode else 'transparent'};color:{color};border:none;font-size:16px;padding:4px 8px;border-radius:6px;}}QPushButton:hover{{background:{C.HOVER};color:{C.WHITE};}}")
self._status_label.setText("Private Mode " + ("ON" if self._private_mode else "OFF"))
def _update_adblock(self):
self._adblock_label.setText(f"ADS: {self.ad_blocker.blocked_count}")
def _show_menu(self):
menu = QMenu(self)
menu.setStyleSheet(f"""
QMenu {{background:{C.PANEL};border:1px solid {C.MUTED};color:{C.TEXT};border-radius:8px;padding:4px;}}
QMenu::item {{padding:8px 20px;border-radius:4px;}}
QMenu::item:selected {{background:{C.HOVER};color:{C.WHITE};}}
QMenu::separator {{height:1px;background:{C.MUTED};margin:4px 8px;}}
""")
menu.addAction("New Tab (Ctrl+T)", self.new_tab)
menu.addAction("Close Tab (Ctrl+W)", lambda: self.close_tab(self._tabs.index(self._stack.currentWidget())))
menu.addSeparator()
# Bookmarks submenu
bm_menu = menu.addMenu("Bookmarks")
bm_menu.setStyleSheet(menu.styleSheet())
for bm in DataManager.get_bookmarks():
bm_menu.addAction(bm['title'], lambda u=bm['url']: self.new_tab(u))
# History submenu
h_menu = menu.addMenu("History")
h_menu.setStyleSheet(menu.styleSheet())
for item in DataManager.get_history()[:15]:
h_menu.addAction(f"{item['title'][:30]}", lambda u=item['url']: self.new_tab(u))
h_menu.addSeparator()
h_menu.addAction("Clear History", DataManager.clear_history)
menu.addSeparator()
menu.addAction("Zoom In (Ctrl++)", self._zoom_in)
menu.addAction("Zoom Out (Ctrl+-)", self._zoom_out)
menu.addAction("Reset Zoom", self._zoom_reset)
menu.addSeparator()
menu.addAction("Find in Page (Ctrl+F)", self._find_in_page)
menu.addAction("Developer Tools (F12)", self._dev_tools)
menu.addAction("Full Screen (F11)", self._fullscreen)
menu.addSeparator()
menu.addAction(f"Ads Blocked: {self.ad_blocker.blocked_count}")
menu.addSeparator()
menu.addAction(f"About {APP_NAME}", self._about)
menu.exec(self.mapToGlobal(QPoint(self.width() - 200, 80)))
def _zoom_in(self):
current = self._stack.currentWidget()
if current:
factor = current.web.zoomFactor() + 0.1
current.web.setZoomFactor(factor)
self._zoom_label.setText(f"{int(factor*100)}%")
def _zoom_out(self):
current = self._stack.currentWidget()
if current:
factor = max(0.3, current.web.zoomFactor() - 0.1)
current.web.setZoomFactor(factor)
self._zoom_label.setText(f"{int(factor*100)}%")
def _zoom_reset(self):
current = self._stack.currentWidget()
if current:
current.web.setZoomFactor(1.0)
self._zoom_label.setText("100%")
def _find_in_page(self):
text, ok = QInputDialog.getText(self, "Find", "Search for:")
if ok and text:
current = self._stack.currentWidget()
if current:
current.web.findText(text)
def _dev_tools(self):
current = self._stack.currentWidget()
if current:
# Open dev tools in new tab
page = current.web.page()
if page:
page.setDevToolsPage(QWebEnginePage())
def _fullscreen(self):
if self.isFullScreen():
self.showNormal()
else:
self.showFullScreen()
def _about(self):
QMessageBox.about(self, f"About {APP_NAME}",
f"{APP_NAME} v{APP_VERSION}\n"
"Created by MoneyPack\n\n"
"Premium Web Browser\n\n"
"Features:\n"
"- Tabbed browsing\n"
"- Built-in ad blocker\n"
"- Speed dial homepage\n"
"- Private browsing mode\n"
"- Bookmarks & history\n"
"- Custom dark theme\n"
f"\nAds blocked this session: {self.ad_blocker.blocked_count}"
)
# ─── SHORTCUTS ───
def _setup_shortcuts(self):
QShortcut(QKeySequence("Ctrl+T"), self, self.new_tab)
QShortcut(QKeySequence("Ctrl+W"), self, lambda: self.close_tab(self._tabs.index(self._stack.currentWidget())) if self._tabs else None)
QShortcut(QKeySequence("Ctrl+L"), self, lambda: (self._url_bar.selectAll(), self._url_bar.setFocus()))
QShortcut(QKeySequence("Ctrl+R"), self, self._reload)
QShortcut(QKeySequence("F5"), self, self._reload)
QShortcut(QKeySequence("Alt+Left"), self, self._go_back)
QShortcut(QKeySequence("Alt+Right"), self, self._go_forward)
QShortcut(QKeySequence("Ctrl++"), self, self._zoom_in)
QShortcut(QKeySequence("Ctrl+-"), self, self._zoom_out)
QShortcut(QKeySequence("Ctrl+0"), self, self._zoom_reset)
QShortcut(QKeySequence("Ctrl+F"), self, self._find_in_page)
QShortcut(QKeySequence("F11"), self, self._fullscreen)
QShortcut(QKeySequence("F12"), self, self._dev_tools)
QShortcut(QKeySequence("Ctrl+D"), self, self._add_bookmark)
# Tab switching
for i in range(9):
QShortcut(QKeySequence(f"Ctrl+{i+1}"), self, lambda idx=i: self.switch_tab(idx))
# ─── TITLEBAR DRAG ───
def _title_press(self, e):
if e.button() == Qt.MouseButton.LeftButton:
self._drag_pos = e.globalPosition().toPoint() - self.frameGeometry().topLeft()
def _title_move(self, e):
if self._drag_pos and e.buttons() == Qt.MouseButton.LeftButton:
self.move(e.globalPosition().toPoint() - self._drag_pos)
def _title_release(self, e):
self._drag_pos = None
# ═══════════════════════════════════════════════════════
# ENTRY
# ═══════════════════════════════════════════════════════
def main():
app = QApplication(sys.argv)
app.setApplicationName(APP_NAME)
app.setFont(QFont("Segoe UI", 10))
window = MoneyPackBrowser()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()