| |
| |
| """ |
| 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 |
| ) |
|
|
|
|
| |
| |
| |
|
|
| 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" |
|
|
| |
| DATA_DIR.mkdir(parents=True, exist_ok=True) |
|
|
| |
| 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_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() |
| |
| |
| for ad_domain in AD_DOMAINS: |
| if ad_domain in host: |
| info.block(True) |
| self.blocked_count += 1 |
| return |
|
|
|
|
| |
| |
| |
|
|
| 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] |
| cls.save_json(HISTORY_FILE, h) |
| |
| @classmethod |
| def clear_history(cls): |
| cls.save_json(HISTORY_FILE, []) |
|
|
|
|
| |
| |
| |
|
|
| 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 = 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_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> |
| """ |
|
|
|
|
| |
| |
| |
|
|
| 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}; }}") |
| |
| |
| self.ad_blocker = AdBlocker() |
| QWebEngineProfile.defaultProfile().setUrlRequestInterceptor(self.ad_blocker) |
| |
| |
| self._tabs = [] |
| self._private_mode = False |
| self._build_ui() |
| self._setup_shortcuts() |
| |
| |
| self.new_tab() |
| |
| def _build_ui(self): |
| central = QWidget() |
| self.setCentralWidget(central) |
| layout = QVBoxLayout(central) |
| layout.setContentsMargins(0, 0, 0, 0) |
| layout.setSpacing(0) |
| |
| |
| 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 = QLabel("MoneyPack") |
| brand.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold)) |
| brand.setStyleSheet(f"color: {C.GOLD};") |
| tb_layout.addWidget(brand) |
| |
| tb_layout.addStretch() |
| |
| |
| 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_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_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 = 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) |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| 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_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) |
| |
| |
| self._stack = QStackedWidget() |
| self._stack.setStyleSheet(f"background: {C.BG};") |
| layout.addWidget(self._stack) |
| |
| |
| 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) |
| |
| |
| self._ad_timer = QTimer(self) |
| self._ad_timer.timeout.connect(self._update_adblock) |
| self._ad_timer.start(2000) |
| |
| |
| |
| def new_tab(self, url=""): |
| tab = BrowserTab(private=self._private_mode) |
| |
| if url: |
| tab.navigate(url) |
| else: |
| |
| tab.web.setHtml(NEW_TAB_HTML) |
| |
| |
| 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): |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| title = tab.web.title() or url |
| DataManager.add_history(title, url) |
| |
| |
| |
| 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() |
| |
| |
| |
| 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() |
| |
| |
| 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)) |
| |
| |
| 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: |
| |
| 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}" |
| ) |
| |
| |
| |
| 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) |
| |
| |
| for i in range(9): |
| QShortcut(QKeySequence(f"Ctrl+{i+1}"), self, lambda idx=i: self.switch_tab(idx)) |
| |
| |
| |
| 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 |
|
|
|
|
| |
| |
| |
|
|
| 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() |
|
|