Compare commits

..

No commits in common. "01997aee90bc03fa4bb0405c06a5b0bf097e0b25" and "c2b2eee0223dfa526b1338ef296a87e1fd7a8f45" have entirely different histories.

14 changed files with 281 additions and 486 deletions

View file

@ -1,15 +1,10 @@
# 0.3.3
* Remove screenshot tool
* Improve width of bug report dialog
* Add Tag relationship visualisation tool
# 0.3.2 # 0.3.2
* Add weekday letters on left axis of Statistics page * Add weekday letters on left axis of Statistics page
* Allow clicking on a date in the Statistics heatmap and have it open that page * Allow clicking on a date in the Statistics heatmap and have it open that page
* Add the ability to choose the database path at startup * Add the ability to choose the database path at startup
* Add in-app bug report functionality * Add in-app bug report functionality
* Add ability to take screenshots in-app and insert them into the page
# 0.3.1 # 0.3.1

View file

@ -28,7 +28,7 @@ report from within the app.
* All changes are version controlled, with ability to view/diff versions and revert * All changes are version controlled, with ability to view/diff versions and revert
* Text is Markdown with basic styling * Text is Markdown with basic styling
* Tabs are supported - right-click on a date from the calendar to open it in a new tab. * Tabs are supported - right-click on a date from the calendar to open it in a new tab.
* Images are supported * Images are supported, as is the ability to take a screenshot and have it insert into the page automatically.
* Search all pages, or find text on page (Ctrl+F) * Search all pages, or find text on page (Ctrl+F)
* Add tags to pages, find pages by tag in the Tag Browser, and customise tag names and colours * Add tags to pages, find pages by tag in the Tag Browser, and customise tag names and colours
* Automatic periodic saving (or explicitly save) * Automatic periodic saving (or explicitly save)

View file

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
import importlib.metadata import importlib.metadata
import requests import requests
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
@ -50,8 +49,6 @@ class BugReportDialog(QDialog):
button_box.rejected.connect(self.reject) button_box.rejected.connect(self.reject)
layout.addWidget(button_box) layout.addWidget(button_box)
self.setMinimumWidth(560)
self.text_edit.setFocus() self.text_edit.setFocus()
# ------------Helpers ------------ # # ------------Helpers ------------ #

View file

@ -746,49 +746,6 @@ class DBManager:
revisions_by_date, revisions_by_date,
) )
def get_tag_cooccurrences(self):
"""
Compute tagtag co-occurrence across pages.
Returns:
tags_by_id: dict[int, TagRow] # id -> (id, name, color)
edges: list[(int, int, int)] # (tag_id1, tag_id2, page_count)
tag_page_counts: dict[int, int] # tag_id -> number of pages it appears on
"""
cur = self.conn.cursor()
# 1) All tags (reuse existing helper)
all_tags: list[TagRow] = self.list_tags()
tags_by_id: dict[int, TagRow] = {t[0]: t for t in all_tags}
# 2) How many pages each tag appears on (for node sizing)
rows = cur.execute(
"""
SELECT tag_id, COUNT(DISTINCT page_date) AS c
FROM page_tags
GROUP BY tag_id;
"""
).fetchall()
tag_page_counts = {r["tag_id"]: r["c"] for r in rows}
# 3) Co-occurrence of tag pairs on the same page
rows = cur.execute(
"""
SELECT
pt1.tag_id AS tag1,
pt2.tag_id AS tag2,
COUNT(DISTINCT pt1.page_date) AS c
FROM page_tags AS pt1
JOIN page_tags AS pt2
ON pt1.page_date = pt2.page_date
AND pt1.tag_id < pt2.tag_id
GROUP BY pt1.tag_id, pt2.tag_id;
""",
).fetchall()
edges = [(r["tag1"], r["tag2"], r["c"]) for r in rows]
return tags_by_id, edges, tag_page_counts
def close(self) -> None: def close(self) -> None:
if self.conn is not None: if self.conn is not None:
self.conn.close() self.conn.close()

View file

@ -135,7 +135,6 @@
"delete_tag": "Delete tag", "delete_tag": "Delete tag",
"delete_tag_confirm": "Are you sure you want to delete the tag '{name}'? This will remove it from all pages.", "delete_tag_confirm": "Are you sure you want to delete the tag '{name}'? This will remove it from all pages.",
"tag_already_exists_with_that_name": "A tag already exists with that name", "tag_already_exists_with_that_name": "A tag already exists with that name",
"tag_graph": "Tag relationship graph",
"statistics": "Statistics", "statistics": "Statistics",
"main_window_statistics_accessible_flag": "Stat&istics", "main_window_statistics_accessible_flag": "Stat&istics",
"stats_pages_with_content": "Pages with content (current version)", "stats_pages_with_content": "Pages with content (current version)",
@ -155,5 +154,9 @@
"bug_report_empty": "Please enter some details about the bug before sending.", "bug_report_empty": "Please enter some details about the bug before sending.",
"bug_report_send_failed": "Could not send bug report.", "bug_report_send_failed": "Could not send bug report.",
"bug_report_sent_ok": "Bug report sent. Thank you!", "bug_report_sent_ok": "Bug report sent. Thank you!",
"send": "Send" "send": "Send",
"screenshot": "Take screenshot",
"screenshot_could_not_save": "Could not save screenshot",
"screenshot_get_ready": "You will have five seconds to position your screen before a preview is taken.\n\nYou will be able to click and drag to select a specific region of the preview.",
"screenshot_click_and_drag": "Click and drag to select a region or press enter to accept the whole region"
} }

View file

@ -978,6 +978,7 @@ class MainWindow(QMainWindow):
tb.historyRequested.connect(self._open_history) tb.historyRequested.connect(self._open_history)
tb.insertImageRequested.connect(self._on_insert_image) tb.insertImageRequested.connect(self._on_insert_image)
tb.insertScreenshotRequested.connect(self._start_screenshot)
self._toolbar_bound = True self._toolbar_bound = True
@ -1051,6 +1052,21 @@ class MainWindow(QMainWindow):
for path_str in paths: for path_str in paths:
self.editor.insert_image_from_path(Path(path_str)) self.editor.insert_image_from_path(Path(path_str))
# ----------- Screenshot handler ------------#
def _start_screenshot(self):
ready = QMessageBox.information(
self,
strings._("screenshot"),
strings._("screenshot_get_ready"),
QMessageBox.Ok,
)
if ready == QMessageBox.Ok:
QGuiApplication.processEvents()
QTimer.singleShot(5000, self._do_screenshot_capture)
def _do_screenshot_capture(self):
self.editor.take_screenshot()
# ----------- Tags handler ----------------# # ----------- Tags handler ----------------#
def _update_tag_views_for_date(self, date_iso: str): def _update_tag_views_for_date(self, date_iso: str):
if hasattr(self, "tags"): if hasattr(self, "tags"):

View file

@ -16,9 +16,10 @@ from PySide6.QtGui import (
QTextImageFormat, QTextImageFormat,
QDesktopServices, QDesktopServices,
) )
from PySide6.QtCore import Qt, QRect, QTimer, QUrl from PySide6.QtCore import Qt, QRect, QTimer, QUrl, QStandardPaths
from PySide6.QtWidgets import QTextEdit from PySide6.QtWidgets import QTextEdit
from .screenshot import ScreenshotMarkdownInserter
from .theme import ThemeManager from .theme import ThemeManager
from .markdown_highlighter import MarkdownHighlighter from .markdown_highlighter import MarkdownHighlighter
@ -1064,3 +1065,8 @@ class MarkdownEditor(QTextEdit):
cursor = self.textCursor() cursor = self.textCursor()
cursor.insertImage(img_format) cursor.insertImage(img_format)
cursor.insertText("\n") # Add newline after image cursor.insertText("\n") # Add newline after image
def take_screenshot(self):
images_dir = QStandardPaths.writableLocation(QStandardPaths.PicturesLocation)
self._screenshot_helper = ScreenshotMarkdownInserter(self, images_dir, self)
self._screenshot_helper.capture_and_insert()

164
bouquin/screenshot.py Normal file
View file

@ -0,0 +1,164 @@
from __future__ import annotations
from pathlib import Path
from PySide6.QtCore import Qt, QRect, QPoint, Signal, QDateTime, QObject
from PySide6.QtGui import QGuiApplication, QPainter, QColor, QCursor, QPixmap
from PySide6.QtWidgets import QWidget, QRubberBand, QMessageBox
from . import strings
class ScreenRegionGrabber(QWidget):
regionCaptured = Signal(QPixmap)
def __init__(self, screenshot_pixmap: QPixmap, parent=None):
super().__init__(parent)
self._screen_pixmap = screenshot_pixmap
self._selection_rect = QRect()
self._origin = QPoint()
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool)
self.setWindowState(Qt.WindowFullScreen)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setCursor(Qt.CrossCursor)
self._rubber_band = QRubberBand(QRubberBand.Rectangle, self)
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self._screen_pixmap)
# Dim everything
painter.fillRect(self.rect(), QColor(0, 0, 0, 120))
# Punch a clear hole for the selection, if there is one
if self._selection_rect.isValid():
painter.setCompositionMode(QPainter.CompositionMode_Clear)
painter.fillRect(self._selection_rect, Qt.transparent)
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
else:
# Placeholder text before first click
painter.setPen(QColor(255, 255, 255, 220))
painter.drawText(
self.rect(),
Qt.AlignCenter,
strings._("screenshot_click_and_drag"),
)
painter.end()
def mousePressEvent(self, event):
if event.button() != Qt.LeftButton:
return
self._origin = event.pos()
self._selection_rect = QRect(self._origin, self._origin)
self._rubber_band.setGeometry(self._selection_rect)
self._rubber_band.show()
def mouseMoveEvent(self, event):
if not self._rubber_band.isVisible():
return
self._selection_rect = QRect(self._origin, event.pos()).normalized()
self._rubber_band.setGeometry(self._selection_rect)
self.update()
def mouseReleaseEvent(self, event):
if event.button() != Qt.LeftButton:
return
if not self._rubber_band.isVisible():
return
self._rubber_band.hide()
rect = self._selection_rect.intersected(self._screen_pixmap.rect())
if rect.isValid():
cropped = self._screen_pixmap.copy(rect)
self.regionCaptured.emit(cropped)
self.close()
def keyPressEvent(self, event):
key = event.key()
# Enter / Return → accept full screen
if key in (Qt.Key_Return, Qt.Key_Enter):
if self._screen_pixmap is not None and not self._screen_pixmap.isNull():
self.regionCaptured.emit(self._screen_pixmap)
self.close()
return
# Esc → cancel (no screenshot)
if key == Qt.Key_Escape:
self.close()
return
# Fallback to default behaviour
super().keyPressEvent(event)
class ScreenshotMarkdownInserter(QObject):
"""
Helper that captures a region of the screen, saves it to `images_dir`,
and inserts a Markdown image reference into the MarkdownEditor.
"""
def __init__(self, editor, images_dir: Path, parent=None):
super().__init__(parent)
self._editor = editor
self._images_dir = Path(images_dir)
self._grabber: ScreenRegionGrabber | None = None
def capture_and_insert(self):
"""
Starts the screen-region selection overlay. When the user finishes,
the screenshot is saved and the Markdown is inserted in the editor.
"""
screen = QGuiApplication.screenAt(QCursor.pos())
if screen is None:
screen = QGuiApplication.primaryScreen()
pixmap = screen.grabWindow(0)
self._grabber = ScreenRegionGrabber(pixmap)
self._grabber.regionCaptured.connect(self._on_region_captured)
self._grabber.show()
# ------------------------------------------------------------------ internals
def _on_region_captured(self, pixmap):
if pixmap is None or pixmap.isNull():
return
# Ensure output directory exists
self._images_dir.mkdir(parents=True, exist_ok=True)
timestamp = QDateTime.currentDateTime().toString("yyyyMMdd_HHmmsszzz")
filename = f"bouquin_screenshot_{timestamp}.png"
full_path = self._images_dir / filename
if not pixmap.save(str(full_path), "PNG"):
QMessageBox.critical(
self,
strings._("screenshot"),
strings._("screenshot_could_not_save"),
)
return
self._insert_markdown_image(full_path)
def _insert_markdown_image(self, path: Path):
"""
Insert image into the MarkdownEditor.
"""
if hasattr(self._editor, "insert_image_from_path"):
self._editor.insert_image_from_path(path)
return
rel = path.name
markdown = f"![screenshot]({rel})"
if hasattr(self._editor, "textCursor"):
cursor = self._editor.textCursor()
cursor.insertText(markdown)
self._editor.setTextCursor(cursor)
else:
self._editor.insertPlainText(markdown)

View file

@ -14,9 +14,8 @@ from PySide6.QtWidgets import (
) )
from .db import DBManager from .db import DBManager
from .tag_graph_dialog import TagGraphDialog
from . import strings
from sqlcipher3.dbapi2 import IntegrityError from sqlcipher3.dbapi2 import IntegrityError
from . import strings
class TagBrowserDialog(QDialog): class TagBrowserDialog(QDialog):
@ -72,10 +71,6 @@ class TagBrowserDialog(QDialog):
self.delete_btn.setEnabled(False) self.delete_btn.setEnabled(False)
btn_row.addWidget(self.delete_btn) btn_row.addWidget(self.delete_btn)
self.tag_graph_btn = QPushButton(strings._("tag_graph"))
self.tag_graph_btn.clicked.connect(self._open_tag_graph)
btn_row.addWidget(self.tag_graph_btn)
btn_row.addStretch(1) btn_row.addStretch(1)
layout.addLayout(btn_row) layout.addLayout(btn_row)
@ -256,9 +251,3 @@ class TagBrowserDialog(QDialog):
self._db.delete_tag(tag_id) self._db.delete_tag(tag_id)
self._populate(None) self._populate(None)
self.tagsModified.emit() self.tagsModified.emit()
# ------------ Tag graph handler --------------- #
def _open_tag_graph(self):
dlg = TagGraphDialog(self._db, self)
dlg.resize(800, 600)
dlg.exec()

View file

@ -1,309 +0,0 @@
import networkx as nx
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore
from PySide6.QtWidgets import QDialog, QVBoxLayout, QToolTip
from PySide6.QtGui import QFont, QCursor, QColor
from .db import DBManager
from . import strings
class DraggableGraphItem(pg.GraphItem):
"""GraphItem where individual nodes can be dragged with the left mouse button,
and hover events can be reported back to the owning dialog.
"""
def __init__(self, on_position_changed=None, on_hover=None, **kwds):
# Our own fields MUST be set before super().__init__ because
# GraphItem.__init__ will call self.setData(...)
self._drag_index = None
self._drag_offset = None
self._on_position_changed = on_position_changed
self._on_hover = on_hover
self.pos = None
self._data_kwargs = {} # cache of last setData kwargs
super().__init__(**kwds)
self.setAcceptHoverEvents(True)
def setData(self, **kwds):
"""Cache kwargs so we don't lose size/adj/brush on drag."""
if "pos" in kwds:
self.pos = kwds["pos"]
self._data_kwargs.update(kwds)
super().setData(**self._data_kwargs)
def mouseDragEvent(self, ev):
# --- start of drag ---
if ev.isStart():
if ev.button() != QtCore.Qt.MouseButton.LeftButton:
ev.ignore()
return
pos = ev.buttonDownPos()
pts = self.scatter.pointsAt(pos)
# pointsAt may return an empty list/array
if pts is None or len(pts) == 0:
ev.ignore()
return
spot = pts[0]
self._drag_index = spot.index()
node_pos = np.array(self.pos[self._drag_index], dtype=float)
if hasattr(pos, "x"):
mouse = np.array([pos.x(), pos.y()], dtype=float)
else:
mouse = np.array(pos, dtype=float)
self._drag_offset = node_pos - mouse
ev.accept()
return
# --- end of drag ---
if ev.isFinish():
self._drag_index = None
self._drag_offset = None
ev.accept()
return
# --- drag in progress ---
if self._drag_index is None:
ev.ignore()
return
pos = ev.pos()
if hasattr(pos, "x"):
mouse = np.array([pos.x(), pos.y()], dtype=float)
else:
mouse = np.array(pos, dtype=float)
new_pos = mouse + self._drag_offset
self.pos[self._drag_index] = new_pos # mutate in-place
# Repaint graph, preserving all the other kwargs (size, adj, colours, ...)
self.setData(pos=self.pos)
if self._on_position_changed is not None:
self._on_position_changed(self.pos)
ev.accept()
def hoverEvent(self, ev):
"""Report which node (if any) is under the mouse while hovering."""
# Leaving the item entirely
if ev.isExit():
if self._on_hover is not None:
self._on_hover(None, ev)
return
pos = ev.pos()
pts = self.scatter.pointsAt(pos)
if pts is None or len(pts) == 0:
if self._on_hover is not None:
self._on_hover(None, ev)
return
idx = pts[0].index()
if self._on_hover is not None:
self._on_hover(idx, ev)
class TagGraphDialog(QDialog):
def __init__(self, db: DBManager, parent=None):
super().__init__(parent)
self.setWindowTitle(strings._("tag_graph"))
layout = QVBoxLayout(self)
self.view = pg.GraphicsLayoutWidget()
layout.addWidget(self.view)
self.plot = self.view.addPlot()
self.plot.hideAxis("bottom")
self.plot.hideAxis("left")
# Dark-ish background, Grafana / neon style
self.view.setBackground("#050816")
self.plot.setMouseEnabled(x=True, y=True)
self.plot.getViewBox().setDefaultPadding(0.15)
# State for tags / edges / labels / halo
self._label_items = []
self._tag_ids = []
self._tag_names = {}
self._tag_page_counts = {}
self._halo_sizes = []
self._halo_brushes = []
self.graph_item = DraggableGraphItem(
on_position_changed=self._on_positions_changed,
on_hover=self._on_hover_index,
)
self.plot.addItem(self.graph_item)
# Separate scatter for "halo" glow behind nodes
self._halo_item = pg.ScatterPlotItem(pxMode=True)
self._halo_item.setZValue(-1) # draw behind nodes/labels
self.plot.addItem(self._halo_item)
self._populate_graph(db)
def _populate_graph(self, db: DBManager):
tags_by_id, edges, tag_page_counts = db.get_tag_cooccurrences()
if not tags_by_id:
return
# Map tag_id -> index
tag_ids = list(tags_by_id.keys())
self._tag_ids = tag_ids
self._tag_page_counts = dict(tag_page_counts)
self._tag_names = {tid: tags_by_id[tid][1] for tid in tag_ids}
idx_of = {tid: i for i, tid in enumerate(tag_ids)}
N = len(tag_ids)
# ---- Layout: prefer a weighted spring layout via networkx (topic islands)
if edges:
G = nx.Graph()
for tid in tag_ids:
G.add_node(tid)
for t1, t2, w in edges:
G.add_edge(t1, t2, weight=w)
pos_dict = nx.spring_layout(G, weight="weight", k=1.2, iterations=80)
pos = np.array([pos_dict[tid] for tid in tag_ids], dtype=float)
else:
# Fallback: random-ish blob
pos = np.random.normal(size=(N, 2))
# Adjacency (edges)
adj = np.array([[idx_of[t1], idx_of[t2]] for t1, t2, _ in edges], dtype=int)
# Node sizes: proportional to how often tag is used
max_pages = max(tag_page_counts.values() or [1])
sizes = np.array(
[10 + 20 * (tag_page_counts.get(tid, 0) / max_pages) for tid in tag_ids],
dtype=float,
)
# ---- Neon-style nodes ----
# Inner fill: dark; outline: tag hex colour
node_brushes = []
node_pens = []
dark_fill = (5, 8, 22, 230) # almost background, slightly lighter
# For halo
halo_sizes = []
halo_brushes = []
for i, tid in enumerate(tag_ids):
_id, name, color = tags_by_id[tid]
# node interior (dark) + bright outline
node_brushes.append(pg.mkBrush(dark_fill))
node_pens.append(pg.mkPen(color, width=2.5))
# halo: semi-transparent version of DB colour, larger than node
qcol = QColor(color)
qcol.setAlpha(90)
halo_brushes.append(pg.mkBrush(qcol))
halo_sizes.append(sizes[i] * 1.8)
self._halo_sizes = halo_sizes
self._halo_brushes = halo_brushes
# ---- Edges: softer neon-ish lines with opacity / width based on co-occurrence ----
if edges:
weights = np.array([w for _, _, w in edges], dtype=float)
max_w = weights.max() if weights.size else 1.0
weight_factors = (weights / max_w).clip(0.0, 1.0)
# bright cyan-ish neon
base_color = (56, 189, 248) # tailwind-ish cyan-400
edge_pens = []
for wf in weight_factors:
alpha = int(40 + 160 * wf) # 40200
width = 0.7 + 2.3 * wf # 0.73.0
edge_pens.append(pg.mkPen((*base_color, alpha), width=width))
else:
edge_pens = None
# Assign data to GraphItem (this will set self.graph_item.pos)
self.graph_item.setData(
pos=pos,
adj=adj,
size=sizes,
symbolBrush=node_brushes,
symbolPen=node_pens,
edgePen=edge_pens,
pxMode=True,
)
# ---- Neon halo layer (behind nodes) ----
xs = [p[0] for p in pos]
ys = [p[1] for p in pos]
self._halo_item.setData(
x=xs,
y=ys,
size=self._halo_sizes,
brush=self._halo_brushes,
pen=None,
)
# ---- Add text labels for each tag ----
self._label_items = [] # reset
font = QFont()
font.setPointSize(8)
for i, tid in enumerate(tag_ids):
_id, name, color = tags_by_id[tid]
label = pg.TextItem(text=name, color=color, anchor=(0.5, 0.5))
label.setFont(font)
self.plot.addItem(label)
self._label_items.append(label)
# Initial placement of labels
self._on_positions_changed(pos)
def _on_positions_changed(self, pos):
"""Called by DraggableGraphItem whenever node positions change."""
if not self._label_items:
return
# Update labels
for i, label in enumerate(self._label_items):
label.setPos(float(pos[i, 0]), float(pos[i, 1]) + 0.30)
# Update halo positions to match nodes
if self._halo_sizes and self._halo_brushes:
xs = [p[0] for p in pos]
ys = [p[1] for p in pos]
self._halo_item.setData(
x=xs,
y=ys,
size=self._halo_sizes,
brush=self._halo_brushes,
pen=None,
)
def _on_hover_index(self, index, ev):
"""Show '<tag>: N pages' when hovering a node."""
if index is None or not self._tag_ids:
QToolTip.hideText()
return
tag_id = self._tag_ids[index]
name = self._tag_names.get(tag_id, "")
count = self._tag_page_counts.get(tag_id, 0)
text = f"{name}: {count} page{'s' if count != 1 else ''}"
QToolTip.showText(QCursor.pos(), text, self)

View file

@ -18,6 +18,7 @@ class ToolBar(QToolBar):
checkboxesRequested = Signal() checkboxesRequested = Signal()
historyRequested = Signal() historyRequested = Signal()
insertImageRequested = Signal() insertImageRequested = Signal()
insertScreenshotRequested = Signal()
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(strings._("toolbar_format"), parent) super().__init__(strings._("toolbar_format"), parent)
@ -81,16 +82,21 @@ class ToolBar(QToolBar):
self.actNumbers.setToolTip(strings._("toolbar_numbered_list")) self.actNumbers.setToolTip(strings._("toolbar_numbered_list"))
self.actNumbers.setCheckable(True) self.actNumbers.setCheckable(True)
self.actNumbers.triggered.connect(self.numbersRequested) self.actNumbers.triggered.connect(self.numbersRequested)
self.actCheckboxes = QAction("", self) self.actCheckboxes = QAction("", self)
self.actCheckboxes.setToolTip(strings._("toolbar_toggle_checkboxes")) self.actCheckboxes.setToolTip(strings._("toolbar_toggle_checkboxes"))
self.actCheckboxes.triggered.connect(self.checkboxesRequested) self.actCheckboxes.triggered.connect(self.checkboxesRequested)
# Images # Images
self.actInsertImg = QAction(strings._("images"), self) self.actInsertImg = QAction("🌄", self)
self.actInsertImg.setToolTip(strings._("insert_images")) self.actInsertImg.setToolTip(strings._("insert_images"))
self.actInsertImg.setShortcut("Ctrl+Shift+I") self.actInsertImg.setShortcut("Ctrl+Shift+I")
self.actInsertImg.triggered.connect(self.insertImageRequested) self.actInsertImg.triggered.connect(self.insertImageRequested)
self.actScreenshot = QAction("📸", self)
self.actScreenshot.setToolTip(strings._("screenshot"))
self.actScreenshot.setShortcut("Ctrl+Shift+O")
self.actScreenshot.triggered.connect(self.insertScreenshotRequested)
# History button # History button
self.actHistory = QAction(strings._("history"), self) self.actHistory = QAction(strings._("history"), self)
self.actHistory.triggered.connect(self.historyRequested) self.actHistory.triggered.connect(self.historyRequested)
@ -130,6 +136,7 @@ class ToolBar(QToolBar):
self.actNumbers, self.actNumbers,
self.actCheckboxes, self.actCheckboxes,
self.actInsertImg, self.actInsertImg,
self.actScreenshot,
self.actHistory, self.actHistory,
] ]
) )

176
poetry.lock generated
View file

@ -257,6 +257,9 @@ files = [
{file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"},
] ]
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras] [package.extras]
toml = ["tomli"] toml = ["tomli"]
@ -274,6 +277,23 @@ files = [
[package.extras] [package.extras]
test = ["pyfakefs", "pytest", "pytest-cov"] test = ["pyfakefs", "pytest", "pytest-cov"]
[[package]]
name = "exceptiongroup"
version = "1.3.0"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
{file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
]
[package.dependencies]
typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
[package.extras]
test = ["pytest (>=6)"]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.11" version = "3.11"
@ -299,90 +319,6 @@ files = [
{file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
] ]
[[package]]
name = "networkx"
version = "3.5"
description = "Python package for creating and manipulating graphs and networks"
optional = false
python-versions = ">=3.11"
files = [
{file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"},
{file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"},
]
[package.extras]
default = ["matplotlib (>=3.8)", "numpy (>=1.25)", "pandas (>=2.0)", "scipy (>=1.11.2)"]
developer = ["mypy (>=1.15)", "pre-commit (>=4.1)"]
doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=10)", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8.0)", "sphinx-gallery (>=0.18)", "texext (>=0.6.7)"]
example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=2.0.0)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"]
extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"]
test = ["pytest (>=7.2)", "pytest-cov (>=4.0)", "pytest-xdist (>=3.0)"]
test-extras = ["pytest-mpl", "pytest-randomly"]
[[package]]
name = "numpy"
version = "2.2.6"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.10"
files = [
{file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"},
{file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"},
{file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"},
{file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"},
{file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"},
{file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"},
{file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"},
{file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"},
{file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"},
{file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"},
{file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"},
{file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"},
{file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"},
{file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"},
{file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"},
{file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"},
{file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"},
{file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"},
{file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"},
{file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"},
{file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"},
{file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"},
{file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"},
{file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"},
{file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"},
{file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"},
{file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"},
{file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"},
{file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"},
{file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"},
{file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"},
{file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"},
{file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"},
{file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"},
{file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"},
{file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"},
{file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"},
{file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"},
{file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"},
{file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"},
{file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"},
{file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"},
{file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"},
{file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"},
{file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"},
{file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"},
{file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"},
{file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"},
{file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"},
{file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"},
{file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"},
{file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"},
{file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"},
{file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"},
{file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"},
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "25.0" version = "25.0"
@ -437,20 +373,7 @@ files = [
[package.dependencies] [package.dependencies]
desktop-entry-lib = "*" desktop-entry-lib = "*"
requests = "*" requests = "*"
tomli = {version = "*", markers = "python_version < \"3.11\""}
[[package]]
name = "pyqtgraph"
version = "0.14.0"
description = "Scientific Graphics and GUI Library for Python"
optional = false
python-versions = ">=3.10"
files = [
{file = "pyqtgraph-0.14.0-py3-none-any.whl", hash = "sha256:7abb7c3e17362add64f8711b474dffac5e7b0e9245abdf992e9a44119b7aa4f5"},
]
[package.dependencies]
colorama = "*"
numpy = ">=1.25.0"
[[package]] [[package]]
name = "pyside6" name = "pyside6"
@ -519,10 +442,12 @@ files = [
[package.dependencies] [package.dependencies]
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
iniconfig = ">=1" iniconfig = ">=1"
packaging = ">=20" packaging = ">=20"
pluggy = ">=1.5,<2" pluggy = ">=1.5,<2"
pygments = ">=2.7.2" pygments = ">=2.7.2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
@ -749,6 +674,57 @@ files = [
{file = "sqlcipher3_wheels-0.5.5.post0.tar.gz", hash = "sha256:2c291ba05fa3e57c9b4d407d2751aa69266b5372468e7402daaa312b251aca7f"}, {file = "sqlcipher3_wheels-0.5.5.post0.tar.gz", hash = "sha256:2c291ba05fa3e57c9b4d407d2751aa69266b5372468e7402daaa312b251aca7f"},
] ]
[[package]]
name = "tomli"
version = "2.3.0"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
files = [
{file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"},
{file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"},
{file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"},
{file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"},
{file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"},
{file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"},
{file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"},
{file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"},
{file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"},
{file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"},
{file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"},
{file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"},
{file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"},
{file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"},
{file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"},
{file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"},
{file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"},
{file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"},
{file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"},
{file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"},
{file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"},
{file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"},
{file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"},
{file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"},
{file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"},
{file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"},
{file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"},
{file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"},
{file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"},
{file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"},
{file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"},
{file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"},
{file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"},
{file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"},
{file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"},
{file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"},
{file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"},
{file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"},
{file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"},
{file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"},
{file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"},
{file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"},
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.15.0" version = "4.15.0"
@ -779,5 +755,5 @@ zstd = ["zstandard (>=0.18.0)"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.11,<3.14" python-versions = ">=3.9,<3.14"
content-hash = "02e508f6d5fc3843084d48a8e495af69555ce6163e281d62b2d3242378e68274" content-hash = "cdb45b4ed472b5fd77e4c5b6ccd88ad4402b4e5473279627177cccb960a29f27"

View file

@ -10,12 +10,10 @@ packages = [{ include = "bouquin" }]
include = ["bouquin/locales/*.json"] include = ["bouquin/locales/*.json"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.11,<3.14" python = ">=3.9,<3.14"
pyside6 = ">=6.8.1,<7.0.0" pyside6 = ">=6.8.1,<7.0.0"
sqlcipher3-wheels = "^0.5.5.post0" sqlcipher3-wheels = "^0.5.5.post0"
requests = "^2.32.5" requests = "^2.32.5"
pyqtgraph = "^0.14.0"
networkx = "^3.5"
[tool.poetry.scripts] [tool.poetry.scripts]
bouquin = "bouquin.__main__:main" bouquin = "bouquin.__main__:main"

View file

@ -3,15 +3,11 @@ from bouquin.flow_layout import FlowLayout
from bouquin.markdown_editor import MarkdownEditor from bouquin.markdown_editor import MarkdownEditor
from bouquin.markdown_highlighter import MarkdownHighlighter from bouquin.markdown_highlighter import MarkdownHighlighter
from bouquin.statistics_dialog import DateHeatMap from bouquin.statistics_dialog import DateHeatMap
from bouquin.tag_graph_dialog import DraggableGraphItem
DBManager.row_factory DBManager.row_factory
DateHeatMap.minimumSizeHint DateHeatMap.minimumSizeHint
DraggableGraphItem.hoverEvent
DraggableGraphItem.mouseDragEvent
FlowLayout.itemAt FlowLayout.itemAt
FlowLayout.expandingDirections FlowLayout.expandingDirections
FlowLayout.hasHeightForWidth FlowLayout.hasHeightForWidth