Add documents feature
This commit is contained in:
parent
23b6ce62a3
commit
422411f12e
18 changed files with 1521 additions and 216 deletions
|
|
@ -1,3 +1,10 @@
|
|||
# 0.6.0
|
||||
|
||||
* Add 'Documents' feature. Documents are tied to Projects in the same way as Time Logging, and can be tagged via the Tags feature.
|
||||
* Close time log dialog if opened via the + button from sidebar widget
|
||||
* Only show tags in Statistics widget if tags are enabled
|
||||
* Fix rounding up/down in Pomodoro timer to the closest 15 min interval
|
||||
|
||||
# 0.5.5
|
||||
|
||||
* Add + button to time log widget in side bar to have a simplified log entry dialog (without summary or report option)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ report from within the app, or optionally to check for new versions to upgrade t
|
|||
* English, French and Italian locales provided
|
||||
* Ability to set reminder alarms (which will be flashed as the reminder)
|
||||
* Ability to log time per day for different projects/activities, pomodoro-style log timer and timesheet reports
|
||||
* Ability to store and tag documents (tied to Projects, same as the Time Logging system). The documents are stored embedded in the encrypted database.
|
||||
|
||||
|
||||
## How to install
|
||||
|
|
|
|||
451
bouquin/db.py
451
bouquin/db.py
|
|
@ -6,11 +6,13 @@ import hashlib
|
|||
import html
|
||||
import json
|
||||
import markdown
|
||||
import mimetypes
|
||||
import re
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from sqlcipher3 import dbapi2 as sqlite
|
||||
from sqlcipher3 import Binary
|
||||
from typing import List, Sequence, Tuple, Dict
|
||||
|
||||
|
||||
|
|
@ -30,6 +32,15 @@ TimeLogRow = Tuple[
|
|||
int, # minutes
|
||||
str | None, # note
|
||||
]
|
||||
DocumentRow = Tuple[
|
||||
int, # id
|
||||
int, # project_id
|
||||
str, # project_name
|
||||
str, # file_name
|
||||
str | None, # description
|
||||
int, # size_bytes
|
||||
str, # uploaded_at (ISO)
|
||||
]
|
||||
|
||||
_TAG_COLORS = [
|
||||
"#FFB3BA", # soft red
|
||||
|
|
@ -65,6 +76,7 @@ class DBConfig:
|
|||
tags: bool = True
|
||||
time_log: bool = True
|
||||
reminders: bool = True
|
||||
documents: bool = True
|
||||
locale: str = "en"
|
||||
font_size: int = 11
|
||||
|
||||
|
|
@ -211,6 +223,35 @@ class DBManager:
|
|||
|
||||
CREATE INDEX IF NOT EXISTS ix_reminders_active
|
||||
ON reminders(active);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_documents (
|
||||
id INTEGER PRIMARY KEY,
|
||||
project_id INTEGER NOT NULL, -- FK to projects.id
|
||||
file_name TEXT NOT NULL, -- original filename
|
||||
mime_type TEXT, -- optional
|
||||
description TEXT,
|
||||
size_bytes INTEGER NOT NULL,
|
||||
uploaded_at TEXT NOT NULL DEFAULT (
|
||||
strftime('%Y-%m-%d','now')
|
||||
),
|
||||
data BLOB NOT NULL,
|
||||
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_project_documents_project
|
||||
ON project_documents(project_id);
|
||||
|
||||
-- New: tags attached to documents (like page_tags, but for docs)
|
||||
CREATE TABLE IF NOT EXISTS document_tags (
|
||||
document_id INTEGER NOT NULL, -- FK to project_documents.id
|
||||
tag_id INTEGER NOT NULL, -- FK to tags.id
|
||||
PRIMARY KEY (document_id, tag_id),
|
||||
FOREIGN KEY(document_id) REFERENCES project_documents(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_document_tags_tag_id
|
||||
ON document_tags(tag_id);
|
||||
"""
|
||||
)
|
||||
self.conn.commit()
|
||||
|
|
@ -248,18 +289,30 @@ class DBManager:
|
|||
).fetchone()
|
||||
return row[0] if row else ""
|
||||
|
||||
def search_entries(self, text: str) -> list[str]:
|
||||
def search_entries(self, text: str) -> list[tuple[str, str, str, str, str | None]]:
|
||||
"""
|
||||
Search for entries by term or tag name.
|
||||
This only works against the latest version of the page.
|
||||
Returns both pages and documents.
|
||||
|
||||
kind = "page" or "document"
|
||||
key = date_iso (page) or str(doc_id) (document)
|
||||
title = heading for the result ("YYYY-MM-DD" or "Document")
|
||||
text = source text for the snippet
|
||||
aux = extra info (file_name for documents, else None)
|
||||
"""
|
||||
cur = self.conn.cursor()
|
||||
q = text.strip()
|
||||
if not q:
|
||||
return []
|
||||
|
||||
pattern = f"%{q.lower()}%"
|
||||
|
||||
rows = cur.execute(
|
||||
results: list[tuple[str, str, str, str, str | None]] = []
|
||||
|
||||
# --- Pages: content or tag matches ---------------------------------
|
||||
page_rows = cur.execute(
|
||||
"""
|
||||
SELECT DISTINCT p.date, v.content
|
||||
SELECT DISTINCT p.date AS date_iso, v.content
|
||||
FROM pages AS p
|
||||
JOIN versions AS v
|
||||
ON v.id = p.current_version_id
|
||||
|
|
@ -276,7 +329,54 @@ class DBManager:
|
|||
""",
|
||||
(pattern, pattern),
|
||||
).fetchall()
|
||||
return [(r[0], r[1]) for r in rows]
|
||||
|
||||
for r in page_rows:
|
||||
date_iso = r["date_iso"]
|
||||
content = r["content"]
|
||||
results.append(("page", date_iso, date_iso, content, None))
|
||||
|
||||
# --- Documents: file name, description, or tag matches -------------
|
||||
doc_rows = cur.execute(
|
||||
"""
|
||||
SELECT DISTINCT
|
||||
d.id AS doc_id,
|
||||
d.file_name AS file_name,
|
||||
d.uploaded_at AS uploaded_at,
|
||||
COALESCE(d.description, '') AS description,
|
||||
COALESCE(t.name, '') AS tag_name
|
||||
FROM project_documents AS d
|
||||
LEFT JOIN document_tags AS dt
|
||||
ON dt.document_id = d.id
|
||||
LEFT JOIN tags AS t
|
||||
ON t.id = dt.tag_id
|
||||
WHERE
|
||||
LOWER(d.file_name) LIKE ?
|
||||
OR LOWER(COALESCE(d.description, '')) LIKE ?
|
||||
OR LOWER(COALESCE(t.name, '')) LIKE ?
|
||||
ORDER BY LOWER(d.file_name);
|
||||
""",
|
||||
(pattern, pattern, pattern),
|
||||
).fetchall()
|
||||
|
||||
for r in doc_rows:
|
||||
doc_id = r["doc_id"]
|
||||
file_name = r["file_name"]
|
||||
description = r["description"] or ""
|
||||
uploaded_at = r["uploaded_at"]
|
||||
# Simple snippet source: file name + description
|
||||
text_src = f"{file_name}\n{description}".strip()
|
||||
|
||||
results.append(
|
||||
(
|
||||
"document",
|
||||
str(doc_id),
|
||||
strings._("search_result_heading_document") + f" ({uploaded_at})",
|
||||
text_src,
|
||||
file_name,
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
def dates_with_content(self) -> list[str]:
|
||||
"""
|
||||
|
|
@ -691,11 +791,12 @@ class DBManager:
|
|||
|
||||
def delete_tag(self, tag_id: int) -> None:
|
||||
"""
|
||||
Delete a tag entirely (removes it from all pages).
|
||||
Delete a tag entirely (removes it from all pages and documents).
|
||||
"""
|
||||
with self.conn:
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("DELETE FROM page_tags WHERE tag_id=?;", (tag_id,))
|
||||
cur.execute("DELETE FROM document_tags WHERE tag_id=?;", (tag_id,))
|
||||
cur.execute("DELETE FROM tags WHERE id=?;", (tag_id,))
|
||||
|
||||
def get_pages_for_tag(self, tag_name: str) -> list[Entry]:
|
||||
|
|
@ -1137,3 +1238,341 @@ class DBManager:
|
|||
cur = self.conn.cursor()
|
||||
cur.execute("DELETE FROM reminders WHERE id = ?", (reminder_id,))
|
||||
self.conn.commit()
|
||||
|
||||
# ------------------------- Documents logic here ------------------------#
|
||||
|
||||
def documents_for_project(self, project_id: int) -> list[DocumentRow]:
|
||||
"""
|
||||
Return metadata for all documents attached to a given project.
|
||||
"""
|
||||
cur = self.conn.cursor()
|
||||
rows = cur.execute(
|
||||
"""
|
||||
SELECT
|
||||
d.id,
|
||||
d.project_id,
|
||||
p.name AS project_name,
|
||||
d.file_name,
|
||||
d.description,
|
||||
d.size_bytes,
|
||||
d.uploaded_at
|
||||
FROM project_documents AS d
|
||||
JOIN projects AS p ON p.id = d.project_id
|
||||
WHERE d.project_id = ?
|
||||
ORDER BY d.uploaded_at DESC, LOWER(d.file_name);
|
||||
""",
|
||||
(project_id,),
|
||||
).fetchall()
|
||||
|
||||
result: list[DocumentRow] = []
|
||||
for r in rows:
|
||||
result.append(
|
||||
(
|
||||
r["id"],
|
||||
r["project_id"],
|
||||
r["project_name"],
|
||||
r["file_name"],
|
||||
r["description"],
|
||||
r["size_bytes"],
|
||||
r["uploaded_at"],
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
def search_documents(self, query: str) -> list[DocumentRow]:
|
||||
"""Search documents across all projects.
|
||||
|
||||
The search is case-insensitive and matches against:
|
||||
- file name
|
||||
- description
|
||||
- project name
|
||||
- tag names associated with the document
|
||||
"""
|
||||
pattern = f"%{query.lower()}%"
|
||||
cur = self.conn.cursor()
|
||||
rows = cur.execute(
|
||||
"""
|
||||
SELECT DISTINCT
|
||||
d.id,
|
||||
d.project_id,
|
||||
p.name AS project_name,
|
||||
d.file_name,
|
||||
d.description,
|
||||
d.size_bytes,
|
||||
d.uploaded_at
|
||||
FROM project_documents AS d
|
||||
LEFT JOIN projects AS p ON p.id = d.project_id
|
||||
LEFT JOIN document_tags AS dt ON dt.document_id = d.id
|
||||
LEFT JOIN tags AS t ON t.id = dt.tag_id
|
||||
WHERE LOWER(d.file_name) LIKE :pat
|
||||
OR LOWER(COALESCE(d.description, '')) LIKE :pat
|
||||
OR LOWER(COALESCE(p.name, '')) LIKE :pat
|
||||
OR LOWER(COALESCE(t.name, '')) LIKE :pat
|
||||
ORDER BY d.uploaded_at DESC, LOWER(d.file_name);
|
||||
""",
|
||||
{"pat": pattern},
|
||||
).fetchall()
|
||||
|
||||
result: list[DocumentRow] = []
|
||||
for r in rows:
|
||||
result.append(
|
||||
(
|
||||
r["id"],
|
||||
r["project_id"],
|
||||
r["project_name"],
|
||||
r["file_name"],
|
||||
r["description"],
|
||||
r["size_bytes"],
|
||||
r["uploaded_at"],
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
def add_document_from_path(
|
||||
self,
|
||||
project_id: int,
|
||||
file_path: str,
|
||||
description: str | None = None,
|
||||
) -> int:
|
||||
"""
|
||||
Read a file from disk and store it as a BLOB in project_documents.
|
||||
"""
|
||||
path = Path(file_path)
|
||||
if not path.is_file():
|
||||
raise ValueError(f"File does not exist: {file_path}")
|
||||
|
||||
data = path.read_bytes()
|
||||
size_bytes = len(data)
|
||||
file_name = path.name
|
||||
mime_type, _ = mimetypes.guess_type(str(path))
|
||||
mime_type = mime_type or None
|
||||
|
||||
with self.conn:
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO project_documents
|
||||
(project_id, file_name, mime_type,
|
||||
description, size_bytes, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(
|
||||
project_id,
|
||||
file_name,
|
||||
mime_type,
|
||||
description,
|
||||
size_bytes,
|
||||
Binary(data),
|
||||
),
|
||||
)
|
||||
doc_id = cur.lastrowid or 0
|
||||
|
||||
return int(doc_id)
|
||||
|
||||
def update_document_description(self, doc_id: int, description: str | None) -> None:
|
||||
with self.conn:
|
||||
self.conn.execute(
|
||||
"UPDATE project_documents SET description = ? WHERE id = ?;",
|
||||
(description, doc_id),
|
||||
)
|
||||
|
||||
def delete_document(self, doc_id: int) -> None:
|
||||
with self.conn:
|
||||
self.conn.execute("DELETE FROM project_documents WHERE id = ?;", (doc_id,))
|
||||
|
||||
def document_data(self, doc_id: int) -> bytes:
|
||||
"""
|
||||
Return just the raw bytes for a document.
|
||||
"""
|
||||
cur = self.conn.cursor()
|
||||
row = cur.execute(
|
||||
"SELECT data FROM project_documents WHERE id = ?;",
|
||||
(doc_id,),
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise KeyError(f"Unknown document id {doc_id}")
|
||||
return bytes(row["data"])
|
||||
|
||||
def get_tags_for_document(self, document_id: int) -> list[TagRow]:
|
||||
"""
|
||||
Return (id, name, color) for all tags attached to this document.
|
||||
"""
|
||||
cur = self.conn.cursor()
|
||||
rows = cur.execute(
|
||||
"""
|
||||
SELECT t.id, t.name, t.color
|
||||
FROM document_tags dt
|
||||
JOIN tags t ON t.id = dt.tag_id
|
||||
WHERE dt.document_id = ?
|
||||
ORDER BY LOWER(t.name);
|
||||
""",
|
||||
(document_id,),
|
||||
).fetchall()
|
||||
return [(r[0], r[1], r[2]) for r in rows]
|
||||
|
||||
def set_tags_for_document(self, document_id: int, tag_names: Sequence[str]) -> None:
|
||||
"""
|
||||
Replace the tag set for a document with the given names.
|
||||
Behaviour mirrors set_tags_for_page.
|
||||
"""
|
||||
# Normalise + dedupe (case-insensitive)
|
||||
clean_names: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for name in tag_names:
|
||||
name = name.strip()
|
||||
if not name:
|
||||
continue
|
||||
key = name.lower()
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
clean_names.append(name)
|
||||
|
||||
with self.conn:
|
||||
cur = self.conn.cursor()
|
||||
|
||||
# Ensure the document exists
|
||||
exists = cur.execute(
|
||||
"SELECT 1 FROM project_documents WHERE id = ?;", (document_id,)
|
||||
).fetchone()
|
||||
if not exists:
|
||||
raise sqlite.IntegrityError(f"Unknown document id {document_id}")
|
||||
|
||||
if not clean_names:
|
||||
cur.execute(
|
||||
"DELETE FROM document_tags WHERE document_id = ?;",
|
||||
(document_id,),
|
||||
)
|
||||
return
|
||||
|
||||
# For each tag name, reuse existing tag (case-insensitive) or create new
|
||||
final_tag_names: list[str] = []
|
||||
for name in clean_names:
|
||||
existing = cur.execute(
|
||||
"SELECT name FROM tags WHERE LOWER(name) = LOWER(?);", (name,)
|
||||
).fetchone()
|
||||
if existing:
|
||||
final_tag_names.append(existing["name"])
|
||||
else:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO tags(name, color)
|
||||
VALUES (?, ?);
|
||||
""",
|
||||
(name, self._default_tag_colour(name)),
|
||||
)
|
||||
final_tag_names.append(name)
|
||||
|
||||
# Lookup ids for the final tag names
|
||||
placeholders = ",".join("?" for _ in final_tag_names)
|
||||
rows = cur.execute(
|
||||
f"""
|
||||
SELECT id, name
|
||||
FROM tags
|
||||
WHERE name IN ({placeholders});
|
||||
""", # nosec
|
||||
tuple(final_tag_names),
|
||||
).fetchall()
|
||||
ids_by_name = {r["name"]: r["id"] for r in rows}
|
||||
|
||||
# Reset document_tags for this document
|
||||
cur.execute(
|
||||
"DELETE FROM document_tags WHERE document_id = ?;",
|
||||
(document_id,),
|
||||
)
|
||||
for name in final_tag_names:
|
||||
tag_id = ids_by_name.get(name)
|
||||
if tag_id is not None:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO document_tags(document_id, tag_id)
|
||||
VALUES (?, ?);
|
||||
""",
|
||||
(document_id, tag_id),
|
||||
)
|
||||
|
||||
def documents_by_date(self) -> Dict[_dt.date, int]:
|
||||
"""
|
||||
Return a mapping of date -> number of documents uploaded on that date.
|
||||
|
||||
The keys are datetime.date objects derived from the
|
||||
project_documents.uploaded_at column, which is stored as a
|
||||
YYYY-MM-DD ISO date string (or a timestamp whose leading part
|
||||
is that date).
|
||||
"""
|
||||
cur = self.conn.cursor()
|
||||
try:
|
||||
rows = cur.execute(
|
||||
"""
|
||||
SELECT uploaded_at AS date_iso,
|
||||
COUNT(*) AS c
|
||||
FROM project_documents
|
||||
WHERE uploaded_at IS NOT NULL
|
||||
AND uploaded_at != ''
|
||||
GROUP BY uploaded_at
|
||||
ORDER BY uploaded_at;
|
||||
"""
|
||||
).fetchall()
|
||||
except Exception:
|
||||
# Older DBs without project_documents/uploaded_at → no document stats
|
||||
return {}
|
||||
|
||||
result: Dict[_dt.date, int] = {}
|
||||
for r in rows:
|
||||
date_iso = r["date_iso"]
|
||||
if not date_iso:
|
||||
continue
|
||||
|
||||
# If uploaded_at ever contains a full timestamp, only use
|
||||
# the leading date portion.
|
||||
date_part = str(date_iso).split(" ", 1)[0][:10]
|
||||
try:
|
||||
d = _dt.date.fromisoformat(date_part)
|
||||
except Exception: # nosec B112
|
||||
continue
|
||||
|
||||
result[d] = int(r["c"])
|
||||
|
||||
return result
|
||||
|
||||
def todays_documents(self, date_iso: str) -> list[tuple[int, str, str | None, str]]:
|
||||
"""
|
||||
Return today's documents as
|
||||
(doc_id, file_name, project_name, uploaded_at).
|
||||
"""
|
||||
cur = self.conn.cursor()
|
||||
rows = cur.execute(
|
||||
"""
|
||||
SELECT d.id AS doc_id,
|
||||
d.file_name AS file_name,
|
||||
p.name AS project_name
|
||||
FROM project_documents AS d
|
||||
LEFT JOIN projects AS p ON p.id = d.project_id
|
||||
WHERE d.uploaded_at LIKE ?
|
||||
ORDER BY d.uploaded_at DESC, LOWER(d.file_name);
|
||||
""",
|
||||
(f"%{date_iso}%",),
|
||||
).fetchall()
|
||||
|
||||
return [(r["doc_id"], r["file_name"], r["project_name"]) for r in rows]
|
||||
|
||||
def get_documents_for_tag(self, tag_name: str) -> list[tuple[int, str, str]]:
|
||||
"""
|
||||
Return (document_id, project_name, file_name) for documents with a given tag.
|
||||
"""
|
||||
cur = self.conn.cursor()
|
||||
rows = cur.execute(
|
||||
"""
|
||||
SELECT d.id AS doc_id,
|
||||
p.name AS project_name,
|
||||
d.file_name
|
||||
FROM project_documents AS d
|
||||
JOIN document_tags AS dt ON dt.document_id = d.id
|
||||
JOIN tags AS t ON t.id = dt.tag_id
|
||||
LEFT JOIN projects AS p ON p.id = d.project_id
|
||||
WHERE LOWER(t.name) = LOWER(?)
|
||||
ORDER BY LOWER(d.file_name);
|
||||
""",
|
||||
(tag_name,),
|
||||
).fetchall()
|
||||
return [(r["doc_id"], r["project_name"], r["file_name"]) for r in rows]
|
||||
|
|
|
|||
594
bouquin/documents.py
Normal file
594
bouquin/documents.py
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import Qt, QUrl
|
||||
from PySide6.QtGui import QDesktopServices, QColor
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QFormLayout,
|
||||
QComboBox,
|
||||
QLineEdit,
|
||||
QTableWidget,
|
||||
QTableWidgetItem,
|
||||
QAbstractItemView,
|
||||
QHeaderView,
|
||||
QPushButton,
|
||||
QFileDialog,
|
||||
QMessageBox,
|
||||
QWidget,
|
||||
QFrame,
|
||||
QToolButton,
|
||||
QListWidget,
|
||||
QListWidgetItem,
|
||||
QSizePolicy,
|
||||
QStyle,
|
||||
)
|
||||
|
||||
from .db import DBManager, DocumentRow
|
||||
from .settings import load_db_config
|
||||
from .time_log import TimeCodeManagerDialog
|
||||
from . import strings
|
||||
|
||||
|
||||
class TodaysDocumentsWidget(QFrame):
|
||||
"""
|
||||
Collapsible sidebar widget showing today's documents.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, db: DBManager, date_iso: str, parent: QWidget | None = None
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self._db = db
|
||||
self._current_date = date_iso
|
||||
|
||||
self.setFrameShape(QFrame.StyledPanel)
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
||||
|
||||
# Header (toggle + open-documents button)
|
||||
self.toggle_btn = QToolButton()
|
||||
self.toggle_btn.setText(strings._("todays_documents"))
|
||||
self.toggle_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
|
||||
self.toggle_btn.setCheckable(True)
|
||||
self.toggle_btn.setChecked(False)
|
||||
self.toggle_btn.setArrowType(Qt.RightArrow)
|
||||
self.toggle_btn.clicked.connect(self._on_toggle)
|
||||
|
||||
self.open_btn = QToolButton()
|
||||
self.open_btn.setIcon(
|
||||
self.style().standardIcon(QStyle.SP_FileDialogDetailedView)
|
||||
)
|
||||
self.open_btn.setToolTip(strings._("project_documents_title"))
|
||||
self.open_btn.setAutoRaise(True)
|
||||
self.open_btn.clicked.connect(self._open_documents_dialog)
|
||||
|
||||
header = QHBoxLayout()
|
||||
header.setContentsMargins(0, 0, 0, 0)
|
||||
header.addWidget(self.toggle_btn)
|
||||
header.addStretch(1)
|
||||
header.addWidget(self.open_btn)
|
||||
|
||||
# Body: list of today's documents
|
||||
self.body = QWidget()
|
||||
body_layout = QVBoxLayout(self.body)
|
||||
body_layout.setContentsMargins(0, 4, 0, 0)
|
||||
body_layout.setSpacing(2)
|
||||
|
||||
self.list = QListWidget()
|
||||
self.list.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.list.setMaximumHeight(160)
|
||||
self.list.itemDoubleClicked.connect(self._open_selected_document)
|
||||
body_layout.addWidget(self.list)
|
||||
|
||||
self.body.setVisible(False)
|
||||
|
||||
main = QVBoxLayout(self)
|
||||
main.setContentsMargins(0, 0, 0, 0)
|
||||
main.addLayout(header)
|
||||
main.addWidget(self.body)
|
||||
|
||||
# Initial fill
|
||||
self.reload()
|
||||
|
||||
# ----- public API ---------------------------------------------------
|
||||
|
||||
def reload(self) -> None:
|
||||
"""Refresh the list of today's documents."""
|
||||
self.list.clear()
|
||||
|
||||
rows = self._db.todays_documents(self._current_date)
|
||||
if not rows:
|
||||
item = QListWidgetItem(strings._("todays_documents_none"))
|
||||
item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
|
||||
self.list.addItem(item)
|
||||
return
|
||||
|
||||
for doc_id, file_name, project_name in rows:
|
||||
label = file_name
|
||||
extra_parts = []
|
||||
if project_name:
|
||||
extra_parts.append(project_name)
|
||||
if extra_parts:
|
||||
label = f"{file_name} – " + " · ".join(extra_parts)
|
||||
|
||||
item = QListWidgetItem(label)
|
||||
item.setData(
|
||||
Qt.ItemDataRole.UserRole,
|
||||
{"doc_id": doc_id, "file_name": file_name},
|
||||
)
|
||||
self.list.addItem(item)
|
||||
|
||||
# ----- internals ----------------------------------------------------
|
||||
|
||||
def set_current_date(self, date_iso: str) -> None:
|
||||
self._current_date = date_iso
|
||||
self.reload()
|
||||
|
||||
def _on_toggle(self, checked: bool) -> None:
|
||||
self.body.setVisible(checked)
|
||||
self.toggle_btn.setArrowType(Qt.DownArrow if checked else Qt.RightArrow)
|
||||
if checked:
|
||||
self.reload()
|
||||
|
||||
def _open_selected_document(self, item: QListWidgetItem) -> None:
|
||||
data = item.data(Qt.ItemDataRole.UserRole)
|
||||
if not isinstance(data, dict):
|
||||
return
|
||||
doc_id = data.get("doc_id")
|
||||
file_name = data.get("file_name") or ""
|
||||
if doc_id is None or not file_name:
|
||||
return
|
||||
self._open_document(int(doc_id), file_name)
|
||||
|
||||
def _open_document(self, doc_id: int, file_name: str) -> None:
|
||||
"""Open a document from the list."""
|
||||
try:
|
||||
data = self._db.document_data(doc_id)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
strings._("project_documents_title"),
|
||||
strings._("documents_open_failed").format(error=str(e)),
|
||||
)
|
||||
return
|
||||
|
||||
suffix = Path(file_name).suffix or ""
|
||||
tmp = tempfile.NamedTemporaryFile(
|
||||
prefix="bouquin_doc_",
|
||||
suffix=suffix,
|
||||
delete=False,
|
||||
)
|
||||
try:
|
||||
tmp.write(data)
|
||||
tmp.flush()
|
||||
finally:
|
||||
tmp.close()
|
||||
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(tmp.name))
|
||||
|
||||
def _open_documents_dialog(self) -> None:
|
||||
"""Open the full DocumentsDialog."""
|
||||
dlg = DocumentsDialog(self._db, self)
|
||||
dlg.exec()
|
||||
# Refresh after any changes
|
||||
self.reload()
|
||||
|
||||
|
||||
class DocumentsDialog(QDialog):
|
||||
"""
|
||||
Per-project document manager.
|
||||
|
||||
- Choose a project
|
||||
- See list of attached documents
|
||||
- Add (from file), open (via temp file), delete
|
||||
- Inline-edit description
|
||||
- Inline-edit tags (comma-separated), using the global tags table
|
||||
"""
|
||||
|
||||
FILE_COL = 0
|
||||
TAGS_COL = 1
|
||||
DESC_COL = 2
|
||||
ADDED_COL = 3
|
||||
SIZE_COL = 4
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: DBManager,
|
||||
parent: QWidget | None = None,
|
||||
initial_project_id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self._db = db
|
||||
self.cfg = load_db_config()
|
||||
self._reloading_docs = False
|
||||
self._search_text: str = ""
|
||||
|
||||
self.setWindowTitle(strings._("project_documents_title"))
|
||||
self.resize(900, 450)
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
|
||||
# --- Project selector -------------------------------------------------
|
||||
form = QFormLayout()
|
||||
proj_row = QHBoxLayout()
|
||||
self.project_combo = QComboBox()
|
||||
self.manage_projects_btn = QPushButton(strings._("manage_projects"))
|
||||
self.manage_projects_btn.clicked.connect(self._manage_projects)
|
||||
proj_row.addWidget(self.project_combo, 1)
|
||||
proj_row.addWidget(self.manage_projects_btn)
|
||||
form.addRow(strings._("project"), proj_row)
|
||||
|
||||
# --- Search box (all projects) ----------------------------------------
|
||||
self.search_edit = QLineEdit()
|
||||
self.search_edit.setClearButtonEnabled(True)
|
||||
self.search_edit.setPlaceholderText(strings._("documents_search_placeholder"))
|
||||
self.search_edit.textChanged.connect(self._on_search_text_changed)
|
||||
form.addRow(strings._("documents_search_label"), self.search_edit)
|
||||
|
||||
root.addLayout(form)
|
||||
|
||||
self.project_combo.currentIndexChanged.connect(self._on_project_changed)
|
||||
|
||||
# --- Table of documents ----------------------------------------------
|
||||
self.table = QTableWidget()
|
||||
self.table.setColumnCount(5)
|
||||
self.table.setHorizontalHeaderLabels(
|
||||
[
|
||||
strings._("documents_col_file"), # FILE_COL
|
||||
strings._("documents_col_tags"), # TAGS_COL
|
||||
strings._("documents_col_description"), # DESC_COL
|
||||
strings._("documents_col_added"), # ADDED_COL
|
||||
strings._("documents_col_size"), # SIZE_COL
|
||||
]
|
||||
)
|
||||
|
||||
header = self.table.horizontalHeader()
|
||||
header.setSectionResizeMode(self.FILE_COL, QHeaderView.Stretch)
|
||||
header.setSectionResizeMode(self.TAGS_COL, QHeaderView.ResizeToContents)
|
||||
header.setSectionResizeMode(self.DESC_COL, QHeaderView.Stretch)
|
||||
header.setSectionResizeMode(self.ADDED_COL, QHeaderView.ResizeToContents)
|
||||
header.setSectionResizeMode(self.SIZE_COL, QHeaderView.ResizeToContents)
|
||||
|
||||
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
# Editable: tags + description
|
||||
self.table.setEditTriggers(
|
||||
QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked
|
||||
)
|
||||
|
||||
self.table.itemChanged.connect(self._on_item_changed)
|
||||
self.table.itemDoubleClicked.connect(self._on_open_clicked)
|
||||
|
||||
root.addWidget(self.table, 1)
|
||||
|
||||
# --- Buttons ---------------------------------------------------------
|
||||
btn_row = QHBoxLayout()
|
||||
btn_row.addStretch(1)
|
||||
|
||||
self.add_btn = QPushButton(strings._("documents_add"))
|
||||
self.add_btn.clicked.connect(self._on_add_clicked)
|
||||
btn_row.addWidget(self.add_btn)
|
||||
|
||||
self.open_btn = QPushButton(strings._("documents_open"))
|
||||
self.open_btn.clicked.connect(self._on_open_clicked)
|
||||
btn_row.addWidget(self.open_btn)
|
||||
|
||||
self.delete_btn = QPushButton(strings._("documents_delete"))
|
||||
self.delete_btn.clicked.connect(self._on_delete_clicked)
|
||||
btn_row.addWidget(self.delete_btn)
|
||||
|
||||
close_btn = QPushButton(strings._("close"))
|
||||
close_btn.clicked.connect(self.accept)
|
||||
btn_row.addWidget(close_btn)
|
||||
|
||||
root.addLayout(btn_row)
|
||||
|
||||
# Separator at bottom (purely cosmetic)
|
||||
line = QFrame()
|
||||
line.setFrameShape(QFrame.HLine)
|
||||
line.setFrameShadow(QFrame.Sunken)
|
||||
root.addWidget(line)
|
||||
|
||||
# Init data
|
||||
self._reload_projects()
|
||||
self._select_initial_project(initial_project_id)
|
||||
self._reload_documents()
|
||||
|
||||
# --- Helpers -------------------------------------------------------------
|
||||
|
||||
def _reload_projects(self) -> None:
|
||||
self.project_combo.blockSignals(True)
|
||||
try:
|
||||
self.project_combo.clear()
|
||||
for proj_id, name in self._db.list_projects():
|
||||
self.project_combo.addItem(name, proj_id)
|
||||
finally:
|
||||
self.project_combo.blockSignals(False)
|
||||
|
||||
def _select_initial_project(self, project_id: Optional[int]) -> None:
|
||||
if project_id is None:
|
||||
if self.project_combo.count() > 0:
|
||||
self.project_combo.setCurrentIndex(0)
|
||||
return
|
||||
|
||||
idx = self.project_combo.findData(project_id)
|
||||
if idx >= 0:
|
||||
self.project_combo.setCurrentIndex(idx)
|
||||
elif self.project_combo.count() > 0:
|
||||
self.project_combo.setCurrentIndex(0)
|
||||
|
||||
def _current_project(self) -> Optional[int]:
|
||||
idx = self.project_combo.currentIndex()
|
||||
if idx < 0:
|
||||
return None
|
||||
proj_id = self.project_combo.itemData(idx)
|
||||
return int(proj_id) if proj_id is not None else None
|
||||
|
||||
def _manage_projects(self) -> None:
|
||||
dlg = TimeCodeManagerDialog(self._db, focus_tab="projects", parent=self)
|
||||
dlg.exec()
|
||||
self._reload_projects()
|
||||
self._reload_documents()
|
||||
|
||||
def _on_search_text_changed(self, text: str) -> None:
|
||||
"""Update the in-memory search text and reload the table."""
|
||||
self._search_text = text
|
||||
self._reload_documents()
|
||||
|
||||
def _reload_documents(self) -> None:
|
||||
|
||||
search = (self._search_text or "").strip()
|
||||
|
||||
self._reloading_docs = True
|
||||
try:
|
||||
self.table.setRowCount(0)
|
||||
|
||||
if search:
|
||||
# Global search across all projects
|
||||
rows: list[DocumentRow] = self._db.search_documents(search)
|
||||
|
||||
else:
|
||||
proj_id = self._current_project()
|
||||
if proj_id is None:
|
||||
return
|
||||
|
||||
rows = self._db.documents_for_project(proj_id)
|
||||
|
||||
self.table.setRowCount(len(rows))
|
||||
|
||||
for row_idx, r in enumerate(rows):
|
||||
(
|
||||
doc_id,
|
||||
_project_id,
|
||||
project_name,
|
||||
file_name,
|
||||
description,
|
||||
size_bytes,
|
||||
uploaded_at,
|
||||
) = r
|
||||
|
||||
# Col 0: File
|
||||
file_item = QTableWidgetItem(file_name)
|
||||
file_item.setData(Qt.ItemDataRole.UserRole, doc_id)
|
||||
file_item.setFlags(file_item.flags() & ~Qt.ItemIsEditable)
|
||||
self.table.setItem(row_idx, self.FILE_COL, file_item)
|
||||
|
||||
# Col 1: Tags (comma-separated)
|
||||
tags = self._db.get_tags_for_document(doc_id)
|
||||
tag_names = [name for (_tid, name, _color) in tags]
|
||||
tags_text = ", ".join(tag_names)
|
||||
tags_item = QTableWidgetItem(tags_text)
|
||||
|
||||
# If there is at least one tag, colour the cell using the first tag's colour
|
||||
if tags:
|
||||
first_color = tags[0][2]
|
||||
if first_color:
|
||||
col = QColor(first_color)
|
||||
tags_item.setBackground(col)
|
||||
# Choose a readable text color
|
||||
if col.lightness() < 128:
|
||||
tags_item.setForeground(QColor("#ffffff"))
|
||||
else:
|
||||
tags_item.setForeground(QColor("#000000"))
|
||||
|
||||
self.table.setItem(row_idx, self.TAGS_COL, tags_item)
|
||||
if not self.cfg.tags:
|
||||
self.table.hideColumn(self.TAGS_COL)
|
||||
|
||||
# Col 2: Description (editable)
|
||||
desc_item = QTableWidgetItem(description or "")
|
||||
self.table.setItem(row_idx, self.DESC_COL, desc_item)
|
||||
|
||||
# Col 3: Added at (not editable)
|
||||
added_label = uploaded_at
|
||||
added_item = QTableWidgetItem(added_label)
|
||||
added_item.setFlags(added_item.flags() & ~Qt.ItemIsEditable)
|
||||
self.table.setItem(row_idx, self.ADDED_COL, added_item)
|
||||
|
||||
# Col 4: Size (not editable)
|
||||
size_item = QTableWidgetItem(self._format_size(size_bytes))
|
||||
size_item.setFlags(size_item.flags() & ~Qt.ItemIsEditable)
|
||||
self.table.setItem(row_idx, self.SIZE_COL, size_item)
|
||||
finally:
|
||||
self._reloading_docs = False
|
||||
|
||||
# --- Signals -------------------------------------------------------------
|
||||
|
||||
def _on_project_changed(self, idx: int) -> None:
|
||||
_ = idx
|
||||
self._reload_documents()
|
||||
|
||||
def _on_add_clicked(self) -> None:
|
||||
proj_id = self._current_project()
|
||||
if proj_id is None:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
strings._("project_documents_title"),
|
||||
strings._("documents_no_project_selected"),
|
||||
)
|
||||
return
|
||||
|
||||
paths, _ = QFileDialog.getOpenFileNames(
|
||||
self,
|
||||
strings._("documents_add"),
|
||||
"",
|
||||
strings._("documents_file_filter_all"),
|
||||
)
|
||||
if not paths:
|
||||
return
|
||||
|
||||
for path in paths:
|
||||
try:
|
||||
self._db.add_document_from_path(proj_id, path)
|
||||
except Exception as e: # pragma: no cover
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
strings._("project_documents_title"),
|
||||
strings._("documents_add_failed").format(error=str(e)),
|
||||
)
|
||||
|
||||
self._reload_documents()
|
||||
|
||||
def _selected_doc_meta(self) -> tuple[Optional[int], Optional[str]]:
|
||||
row = self.table.currentRow()
|
||||
if row < 0:
|
||||
return None, None
|
||||
|
||||
file_item = self.table.item(row, self.FILE_COL)
|
||||
if file_item is None:
|
||||
return None, None
|
||||
|
||||
doc_id = file_item.data(Qt.ItemDataRole.UserRole)
|
||||
file_name = file_item.text()
|
||||
return (int(doc_id) if doc_id is not None else None, file_name)
|
||||
|
||||
def _on_open_clicked(self, *args) -> None:
|
||||
doc_id, file_name = self._selected_doc_meta()
|
||||
if doc_id is None or not file_name:
|
||||
return
|
||||
self._open_document(doc_id, file_name)
|
||||
|
||||
def _on_delete_clicked(self) -> None:
|
||||
doc_id, _file_name = self._selected_doc_meta()
|
||||
if doc_id is None:
|
||||
return
|
||||
|
||||
resp = QMessageBox.question(
|
||||
self,
|
||||
strings._("project_documents_title"),
|
||||
strings._("documents_confirm_delete"),
|
||||
)
|
||||
if resp != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
|
||||
self._db.delete_document(doc_id)
|
||||
self._reload_documents()
|
||||
|
||||
def _on_item_changed(self, item: QTableWidgetItem) -> None:
|
||||
"""
|
||||
Handle inline edits to Description and Tags.
|
||||
"""
|
||||
if self._reloading_docs or item is None:
|
||||
return
|
||||
|
||||
row = item.row()
|
||||
col = item.column()
|
||||
|
||||
file_item = self.table.item(row, self.FILE_COL)
|
||||
if file_item is None:
|
||||
return
|
||||
|
||||
doc_id = file_item.data(Qt.ItemDataRole.UserRole)
|
||||
if doc_id is None:
|
||||
return
|
||||
|
||||
doc_id = int(doc_id)
|
||||
|
||||
# Description column
|
||||
if col == self.DESC_COL:
|
||||
desc = item.text().strip() or None
|
||||
self._db.update_document_description(doc_id, desc)
|
||||
return
|
||||
|
||||
# Tags column
|
||||
if col == self.TAGS_COL:
|
||||
raw = item.text()
|
||||
# split on commas, strip, drop empties
|
||||
names = [p.strip() for p in raw.split(",") if p.strip()]
|
||||
self._db.set_tags_for_document(doc_id, names)
|
||||
|
||||
# Re-normalise text to the canonical tag names stored in DB
|
||||
tags = self._db.get_tags_for_document(doc_id)
|
||||
tag_names = [name for (_tid, name, _color) in tags]
|
||||
tags_text = ", ".join(tag_names)
|
||||
|
||||
self._reloading_docs = True
|
||||
try:
|
||||
item.setText(tags_text)
|
||||
# Reset / apply background based on first tag colour
|
||||
if tags:
|
||||
first_color = tags[0][2]
|
||||
if first_color:
|
||||
col = QColor(first_color)
|
||||
item.setBackground(col)
|
||||
if col.lightness() < 128:
|
||||
item.setForeground(QColor("#ffffff"))
|
||||
else:
|
||||
item.setForeground(QColor("#000000"))
|
||||
else:
|
||||
# No tags: clear background / foreground to defaults
|
||||
item.setBackground(QColor())
|
||||
item.setForeground(QColor())
|
||||
finally:
|
||||
self._reloading_docs = False
|
||||
|
||||
# --- utils -------------------------------------------------------------
|
||||
|
||||
def _open_document(self, doc_id: int, file_name: str) -> None:
|
||||
"""
|
||||
Fetch BLOB from DB, write to a temporary file, and open with default app.
|
||||
"""
|
||||
try:
|
||||
data = self._db.document_data(doc_id)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
strings._("project_documents_title"),
|
||||
strings._("documents_open_failed").format(error=str(e)),
|
||||
)
|
||||
return
|
||||
|
||||
suffix = Path(file_name).suffix or ""
|
||||
tmp = tempfile.NamedTemporaryFile(
|
||||
prefix="bouquin_doc_",
|
||||
suffix=suffix,
|
||||
delete=False,
|
||||
)
|
||||
try:
|
||||
tmp.write(data)
|
||||
tmp.flush()
|
||||
finally:
|
||||
tmp.close()
|
||||
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(tmp.name))
|
||||
|
||||
@staticmethod
|
||||
def _format_size(size_bytes: int) -> str:
|
||||
"""
|
||||
Human-readable file size.
|
||||
"""
|
||||
if size_bytes < 1024:
|
||||
return f"{size_bytes} B"
|
||||
kb = size_bytes / 1024.0
|
||||
if kb < 1024:
|
||||
return f"{kb:.1f} KB"
|
||||
mb = kb / 1024.0
|
||||
if mb < 1024:
|
||||
return f"{mb:.1f} MB"
|
||||
gb = mb / 1024.0
|
||||
return f"{gb:.1f} GB"
|
||||
|
|
@ -142,6 +142,7 @@
|
|||
"tag_browser_instructions": "Click a tag to expand and see all pages with that tag. Click a date to open it. Select a tag to edit its name, change its color, or delete it globally.",
|
||||
"color_hex": "Colour",
|
||||
"date": "Date",
|
||||
"page_or_document": "Page / Document",
|
||||
"add_a_tag": "Add a tag",
|
||||
"edit_tag_name": "Edit tag name",
|
||||
"new_tag_name": "New tag name:",
|
||||
|
|
@ -161,6 +162,9 @@
|
|||
"stats_heatmap_metric": "Colour by",
|
||||
"stats_metric_words": "Words",
|
||||
"stats_metric_revisions": "Revisions",
|
||||
"stats_metric_documents": "Documents",
|
||||
"stats_total_documents": "Total documents",
|
||||
"stats_date_most_documents": "Date with most documents",
|
||||
"stats_no_data": "No statistics available yet.",
|
||||
"select_notebook": "Select notebook",
|
||||
"bug_report_explanation": "Describe what went wrong, what you expected to happen, and any steps to reproduce.\n\nWe do not collect anything else except the Bouquin version number.\n\nIf you wish to be contacted, please leave contact information.\n\nYour request will be sent over HTTPS.",
|
||||
|
|
@ -261,6 +265,7 @@
|
|||
"enable_tags_feature": "Enable Tags",
|
||||
"enable_time_log_feature": "Enable Time Logging",
|
||||
"enable_reminders_feature": "Enable Reminders",
|
||||
"enable_documents_feature": "Enable storing of documents",
|
||||
"pomodoro_time_log_default_text": "Focus session",
|
||||
"toolbar_pomodoro_timer": "Time-logging timer",
|
||||
"set_code_language": "Set code language",
|
||||
|
|
@ -293,5 +298,28 @@
|
|||
"sunday": "Sunday",
|
||||
"day": "Day",
|
||||
"edit_code_block": "Edit code block",
|
||||
"delete_code_block": "Delete code block"
|
||||
"delete_code_block": "Delete code block",
|
||||
"search_result_heading_document": "Document",
|
||||
"toolbar_documents": "Documents Manager",
|
||||
"project_documents_title": "Project documents",
|
||||
"documents_col_file": "File",
|
||||
"documents_col_description": "Description",
|
||||
"documents_col_added": "Added",
|
||||
"documents_col_path": "Path",
|
||||
"documents_col_tags": "Tags",
|
||||
"documents_col_size": "Size",
|
||||
"documents_add": "&Add",
|
||||
"documents_add_document": "Add a document",
|
||||
"documents_open": "&Open",
|
||||
"documents_delete": "&Delete",
|
||||
"documents_no_project_selected": "Please choose a project first.",
|
||||
"documents_file_filter_all": "All files (*)",
|
||||
"documents_add_failed": "Could not add document: {error}",
|
||||
"documents_open_failed": "Could not open document: {error}",
|
||||
"documents_missing_file": "The file does not exist:\n{path}",
|
||||
"documents_confirm_delete": "Remove this document from the project?\n(The file on disk will not be deleted.)",
|
||||
"documents_search_label": "Search",
|
||||
"documents_search_placeholder": "Type to search documents (all projects)",
|
||||
"todays_documents": "Documents from this day",
|
||||
"todays_documents_none": "No documents yet."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ from PySide6.QtWidgets import (
|
|||
|
||||
from .bug_report_dialog import BugReportDialog
|
||||
from .db import DBManager
|
||||
from .documents import DocumentsDialog, TodaysDocumentsWidget
|
||||
from .find_bar import FindBar
|
||||
from .history_dialog import HistoryDialog
|
||||
from .key_prompt import KeyPrompt
|
||||
|
|
@ -126,6 +127,8 @@ class MainWindow(QMainWindow):
|
|||
left_layout.addWidget(self.calendar)
|
||||
left_layout.addWidget(self.search)
|
||||
left_layout.addWidget(self.upcoming_reminders)
|
||||
self.todays_documents = TodaysDocumentsWidget(self.db, self._current_date_iso())
|
||||
left_layout.addWidget(self.todays_documents)
|
||||
left_layout.addWidget(self.time_log)
|
||||
left_layout.addWidget(self.tags)
|
||||
left_panel.setFixedWidth(self.calendar.sizeHint().width() + 16)
|
||||
|
|
@ -335,6 +338,9 @@ class MainWindow(QMainWindow):
|
|||
if not self.cfg.reminders:
|
||||
self.upcoming_reminders.hide()
|
||||
self.toolBar.actAlarm.setVisible(False)
|
||||
if not self.cfg.documents:
|
||||
self.todays_documents.hide()
|
||||
self.toolBar.actDocuments.setVisible(False)
|
||||
|
||||
# Restore window position from settings
|
||||
self._restore_window_position()
|
||||
|
|
@ -1091,6 +1097,7 @@ class MainWindow(QMainWindow):
|
|||
self._tb_checkboxes = lambda: self._call_editor("toggle_checkboxes")
|
||||
self._tb_alarm = self._on_alarm_requested
|
||||
self._tb_timer = self._on_timer_requested
|
||||
self._tb_documents = self._on_documents_requested
|
||||
self._tb_font_larger = self._on_font_larger_requested
|
||||
self._tb_font_smaller = self._on_font_smaller_requested
|
||||
|
||||
|
|
@ -1104,6 +1111,7 @@ class MainWindow(QMainWindow):
|
|||
tb.checkboxesRequested.connect(self._tb_checkboxes)
|
||||
tb.alarmRequested.connect(self._tb_alarm)
|
||||
tb.timerRequested.connect(self._tb_timer)
|
||||
tb.documentsRequested.connect(self._tb_documents)
|
||||
tb.insertImageRequested.connect(self._on_insert_image)
|
||||
tb.historyRequested.connect(self._open_history)
|
||||
tb.fontSizeLargerRequested.connect(self._tb_font_larger)
|
||||
|
|
@ -1320,6 +1328,14 @@ class MainWindow(QMainWindow):
|
|||
timer.start(msecs)
|
||||
self._reminder_timers.append(timer)
|
||||
|
||||
# ----------- Documents handler ------------#
|
||||
def _on_documents_requested(self):
|
||||
documents_dlg = DocumentsDialog(self.db, self)
|
||||
documents_dlg.exec()
|
||||
# Refresh recent documents after any changes
|
||||
if hasattr(self, "todays_documents"):
|
||||
self.todays_documents.reload()
|
||||
|
||||
# ----------- History handler ------------#
|
||||
def _open_history(self):
|
||||
if hasattr(self.editor, "current_date"):
|
||||
|
|
@ -1354,6 +1370,8 @@ class MainWindow(QMainWindow):
|
|||
self.tags.set_current_date(date_iso)
|
||||
if hasattr(self, "time_log"):
|
||||
self.time_log.set_current_date(date_iso)
|
||||
if hasattr(self, "todays_documents"):
|
||||
self.todays_documents.set_current_date(date_iso)
|
||||
|
||||
def _on_tag_added(self):
|
||||
"""Called when a tag is added - trigger autosave for current page"""
|
||||
|
|
@ -1421,6 +1439,7 @@ class MainWindow(QMainWindow):
|
|||
self.cfg.tags = getattr(new_cfg, "tags", self.cfg.tags)
|
||||
self.cfg.time_log = getattr(new_cfg, "time_log", self.cfg.time_log)
|
||||
self.cfg.reminders = getattr(new_cfg, "reminders", self.cfg.reminders)
|
||||
self.cfg.documents = getattr(new_cfg, "documents", self.cfg.documents)
|
||||
self.cfg.locale = getattr(new_cfg, "locale", self.cfg.locale)
|
||||
self.cfg.font_size = getattr(new_cfg, "font_size", self.cfg.font_size)
|
||||
|
||||
|
|
@ -1458,6 +1477,12 @@ class MainWindow(QMainWindow):
|
|||
else:
|
||||
self.upcoming_reminders.show()
|
||||
self.toolBar.actAlarm.setVisible(True)
|
||||
if not self.cfg.documents:
|
||||
self.todays_documents.hide()
|
||||
self.toolBar.actDocuments.setVisible(False)
|
||||
else:
|
||||
self.todays_documents.show()
|
||||
self.toolBar.actDocuments.setVisible(True)
|
||||
|
||||
# ------------ Statistics handler --------------- #
|
||||
|
||||
|
|
|
|||
|
|
@ -129,8 +129,9 @@ class PomodoroManager:
|
|||
|
||||
def _on_timer_stopped(self, elapsed_seconds: int, task_text: str, date_iso: str):
|
||||
"""Handle timer stop - open time log dialog with pre-filled data."""
|
||||
# Convert seconds to decimal hours, rounded up
|
||||
hours = math.ceil(elapsed_seconds / 360) / 25 # Round up to nearest 0.25 hour
|
||||
# Convert seconds to decimal hours, rounding up to the nearest 0.25 hour (15 minutes)
|
||||
quarter_hours = math.ceil(elapsed_seconds / 900)
|
||||
hours = quarter_hours * 0.25
|
||||
|
||||
# Ensure minimum of 0.25 hours
|
||||
if hours < 0.25:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from PySide6.QtWidgets import (
|
|||
|
||||
from . import strings
|
||||
|
||||
Row = Tuple[str, str]
|
||||
Row = Tuple[str, str, str, str, str | None]
|
||||
|
||||
|
||||
class Search(QWidget):
|
||||
|
|
@ -52,9 +52,55 @@ class Search(QWidget):
|
|||
lay.addWidget(self.results)
|
||||
|
||||
def _open_selected(self, item: QListWidgetItem):
|
||||
date_str = item.data(Qt.ItemDataRole.UserRole)
|
||||
if date_str:
|
||||
self.openDateRequested.emit(date_str)
|
||||
data = item.data(Qt.ItemDataRole.UserRole)
|
||||
if not isinstance(data, dict):
|
||||
return
|
||||
|
||||
kind = data.get("kind")
|
||||
if kind == "page":
|
||||
date_iso = data.get("date")
|
||||
if date_iso:
|
||||
self.openDateRequested.emit(date_iso)
|
||||
elif kind == "document":
|
||||
doc_id = data.get("doc_id")
|
||||
file_name = data.get("file_name") or "document"
|
||||
if doc_id is None:
|
||||
return
|
||||
self._open_document(int(doc_id), file_name)
|
||||
|
||||
def _open_document(self, doc_id: int, file_name: str) -> None:
|
||||
"""
|
||||
Open a document search result via a temp file.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from PySide6.QtCore import QUrl
|
||||
from PySide6.QtGui import QDesktopServices
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
try:
|
||||
data = self._db.document_data(doc_id)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
strings._("project_documents_title"),
|
||||
strings._("documents_open_failed").format(error=str(e)),
|
||||
)
|
||||
return
|
||||
|
||||
suffix = Path(file_name).suffix or ""
|
||||
tmp = tempfile.NamedTemporaryFile(
|
||||
prefix="bouquin_doc_",
|
||||
suffix=suffix,
|
||||
delete=False,
|
||||
)
|
||||
try:
|
||||
tmp.write(data)
|
||||
tmp.flush()
|
||||
finally:
|
||||
tmp.close()
|
||||
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(tmp.name))
|
||||
|
||||
def _search(self, text: str):
|
||||
"""
|
||||
|
|
@ -80,28 +126,28 @@ class Search(QWidget):
|
|||
self.resultDatesChanged.emit([]) # clear highlights
|
||||
return
|
||||
|
||||
self.resultDatesChanged.emit(sorted({d for d, _ in rows}))
|
||||
# Only highlight calendar dates for page results
|
||||
page_dates = sorted(
|
||||
{key for (kind, key, _title, _text, _aux) in rows if kind == "page"}
|
||||
)
|
||||
self.resultDatesChanged.emit(page_dates)
|
||||
self.results.show()
|
||||
|
||||
for date_str, content in rows:
|
||||
# Build an HTML fragment around the match and whether to show ellipses
|
||||
frag_html = self._make_html_snippet(content, query, radius=30, maxlen=90)
|
||||
# ---- Per-item widget: date on top, preview row below (with ellipses) ----
|
||||
for kind, key, title, text, aux in rows:
|
||||
# Build an HTML fragment around the match
|
||||
frag_html = self._make_html_snippet(text, query, radius=30, maxlen=90)
|
||||
|
||||
container = QWidget()
|
||||
outer = QVBoxLayout(container)
|
||||
outer.setContentsMargins(8, 6, 8, 6)
|
||||
outer.setContentsMargins(0, 0, 0, 0)
|
||||
outer.setSpacing(2)
|
||||
|
||||
# Date label (plain text)
|
||||
date_lbl = QLabel()
|
||||
date_lbl.setTextFormat(Qt.TextFormat.RichText)
|
||||
date_lbl.setText(f"<h3><i>{date_str}</i></h3>")
|
||||
date_f = date_lbl.font()
|
||||
date_f.setPointSizeF(date_f.pointSizeF() + 1)
|
||||
date_lbl.setFont(date_f)
|
||||
outer.addWidget(date_lbl)
|
||||
# ---- Heading (date for pages, "Document" for docs) ----
|
||||
heading = QLabel(title)
|
||||
heading.setStyleSheet("font-weight:bold;")
|
||||
outer.addWidget(heading)
|
||||
|
||||
# Preview row with optional ellipses
|
||||
# ---- Preview row ----
|
||||
row = QWidget()
|
||||
h = QHBoxLayout(row)
|
||||
h.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -117,9 +163,9 @@ class Search(QWidget):
|
|||
else "<span style='color:#888'>(no preview)</span>"
|
||||
)
|
||||
h.addWidget(preview, 1)
|
||||
|
||||
outer.addWidget(row)
|
||||
|
||||
# Separator line
|
||||
line = QFrame()
|
||||
line.setFrameShape(QFrame.HLine)
|
||||
line.setFrameShadow(QFrame.Sunken)
|
||||
|
|
@ -127,9 +173,22 @@ class Search(QWidget):
|
|||
|
||||
# ---- Add to list ----
|
||||
item = QListWidgetItem()
|
||||
item.setData(Qt.ItemDataRole.UserRole, date_str)
|
||||
item.setSizeHint(container.sizeHint())
|
||||
if kind == "page":
|
||||
item.setData(
|
||||
Qt.ItemDataRole.UserRole,
|
||||
{"kind": "page", "date": key},
|
||||
)
|
||||
else: # document
|
||||
item.setData(
|
||||
Qt.ItemDataRole.UserRole,
|
||||
{
|
||||
"kind": "document",
|
||||
"doc_id": int(key),
|
||||
"file_name": aux or "",
|
||||
},
|
||||
)
|
||||
|
||||
item.setSizeHint(container.sizeHint())
|
||||
self.results.addItem(item)
|
||||
self.results.setItemWidget(item, container)
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ def load_db_config() -> DBConfig:
|
|||
tags = s.value("ui/tags", True, type=bool)
|
||||
time_log = s.value("ui/time_log", True, type=bool)
|
||||
reminders = s.value("ui/reminders", True, type=bool)
|
||||
documents = s.value("ui/documents", True, type=bool)
|
||||
locale = s.value("ui/locale", "en", type=str)
|
||||
font_size = s.value("ui/font_size", 11, type=int)
|
||||
return DBConfig(
|
||||
|
|
@ -55,6 +56,7 @@ def load_db_config() -> DBConfig:
|
|||
tags=tags,
|
||||
time_log=time_log,
|
||||
reminders=reminders,
|
||||
documents=documents,
|
||||
locale=locale,
|
||||
font_size=font_size,
|
||||
)
|
||||
|
|
@ -70,5 +72,6 @@ def save_db_config(cfg: DBConfig) -> None:
|
|||
s.setValue("ui/tags", str(cfg.tags))
|
||||
s.setValue("ui/time_log", str(cfg.time_log))
|
||||
s.setValue("ui/reminders", str(cfg.reminders))
|
||||
s.setValue("ui/documents", str(cfg.documents))
|
||||
s.setValue("ui/locale", str(cfg.locale))
|
||||
s.setValue("ui/font_size", str(cfg.font_size))
|
||||
|
|
|
|||
|
|
@ -181,6 +181,11 @@ class SettingsDialog(QDialog):
|
|||
self.reminders.setCursor(Qt.PointingHandCursor)
|
||||
features_layout.addWidget(self.reminders)
|
||||
|
||||
self.documents = QCheckBox(strings._("enable_documents_feature"))
|
||||
self.documents.setChecked(self.current_settings.documents)
|
||||
self.documents.setCursor(Qt.PointingHandCursor)
|
||||
features_layout.addWidget(self.documents)
|
||||
|
||||
layout.addWidget(features_group)
|
||||
layout.addStretch()
|
||||
return page
|
||||
|
|
@ -308,6 +313,7 @@ class SettingsDialog(QDialog):
|
|||
tags=self.tags.isChecked(),
|
||||
time_log=self.time_log.isChecked(),
|
||||
reminders=self.reminders.isChecked(),
|
||||
documents=self.documents.isChecked(),
|
||||
locale=self.locale_combobox.currentText(),
|
||||
font_size=self.font_size.value(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from PySide6.QtWidgets import (
|
|||
|
||||
from . import strings
|
||||
from .db import DBManager
|
||||
from .settings import load_db_config
|
||||
|
||||
|
||||
# ---------- Activity heatmap ----------
|
||||
|
|
@ -265,6 +266,32 @@ class StatisticsDialog(QDialog):
|
|||
revisions_by_date,
|
||||
) = self._gather_stats()
|
||||
|
||||
# Optional: per-date document counts for the heatmap.
|
||||
# This uses project_documents.uploaded_at aggregated by day, if the
|
||||
# Documents feature is enabled.
|
||||
self.cfg = load_db_config()
|
||||
documents_by_date: Dict[_dt.date, int] = {}
|
||||
total_documents = 0
|
||||
date_most_documents: _dt.date | None = None
|
||||
date_most_documents_count = 0
|
||||
|
||||
if self.cfg.documents:
|
||||
try:
|
||||
documents_by_date = self._db.documents_by_date() or {}
|
||||
except Exception:
|
||||
documents_by_date = {}
|
||||
|
||||
if documents_by_date:
|
||||
total_documents = sum(documents_by_date.values())
|
||||
# Choose the date with the highest count, tie-breaking by earliest date.
|
||||
date_most_documents, date_most_documents_count = sorted(
|
||||
documents_by_date.items(),
|
||||
key=lambda item: (-item[1], item[0]),
|
||||
)[0]
|
||||
|
||||
# for the heatmap
|
||||
self._documents_by_date = documents_by_date
|
||||
|
||||
# --- Numeric summary at the top ----------------------------------
|
||||
form = QFormLayout()
|
||||
root.addLayout(form)
|
||||
|
|
@ -291,7 +318,8 @@ class StatisticsDialog(QDialog):
|
|||
QLabel(str(total_words)),
|
||||
)
|
||||
|
||||
# Unique tag names
|
||||
# Tags
|
||||
if self.cfg.tags:
|
||||
form.addRow(
|
||||
strings._("stats_unique_tags"),
|
||||
QLabel(str(unique_tags)),
|
||||
|
|
@ -305,8 +333,24 @@ class StatisticsDialog(QDialog):
|
|||
else:
|
||||
form.addRow(strings._("stats_page_most_tags"), QLabel("—"))
|
||||
|
||||
# Documents
|
||||
if date_most_documents:
|
||||
form.addRow(
|
||||
strings._("stats_total_documents"),
|
||||
QLabel(str(total_documents)),
|
||||
)
|
||||
|
||||
doc_most_label = (
|
||||
f"{date_most_documents.isoformat()} ({date_most_documents_count})"
|
||||
)
|
||||
|
||||
form.addRow(
|
||||
strings._("stats_date_most_documents"),
|
||||
QLabel(doc_most_label),
|
||||
)
|
||||
|
||||
# --- Heatmap with switcher ---------------------------------------
|
||||
if words_by_date or revisions_by_date:
|
||||
if words_by_date or revisions_by_date or documents_by_date:
|
||||
group = QGroupBox(strings._("stats_activity_heatmap"))
|
||||
group_layout = QVBoxLayout(group)
|
||||
|
||||
|
|
@ -316,6 +360,10 @@ class StatisticsDialog(QDialog):
|
|||
self.metric_combo = QComboBox()
|
||||
self.metric_combo.addItem(strings._("stats_metric_words"), "words")
|
||||
self.metric_combo.addItem(strings._("stats_metric_revisions"), "revisions")
|
||||
if documents_by_date:
|
||||
self.metric_combo.addItem(
|
||||
strings._("stats_metric_documents"), "documents"
|
||||
)
|
||||
combo_row.addWidget(self.metric_combo)
|
||||
combo_row.addStretch(1)
|
||||
group_layout.addLayout(combo_row)
|
||||
|
|
@ -344,6 +392,8 @@ class StatisticsDialog(QDialog):
|
|||
def _apply_metric(self, metric: str) -> None:
|
||||
if metric == "revisions":
|
||||
self._heatmap.set_data(self._revisions_by_date)
|
||||
elif metric == "documents":
|
||||
self._heatmap.set_data(self._documents_by_date)
|
||||
else:
|
||||
self._heatmap.set_data(self._words_by_date)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QColor
|
||||
from PySide6.QtCore import Qt, Signal, QUrl
|
||||
from PySide6.QtGui import QColor, QDesktopServices
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QVBoxLayout,
|
||||
|
|
@ -13,7 +13,11 @@ from PySide6.QtWidgets import (
|
|||
QInputDialog,
|
||||
)
|
||||
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
from .db import DBManager
|
||||
from .settings import load_db_config
|
||||
from . import strings
|
||||
from sqlcipher3.dbapi2 import IntegrityError
|
||||
|
||||
|
|
@ -25,6 +29,7 @@ class TagBrowserDialog(QDialog):
|
|||
def __init__(self, db: DBManager, parent=None, focus_tag: str | None = None):
|
||||
super().__init__(parent)
|
||||
self._db = db
|
||||
self.cfg = load_db_config()
|
||||
self.setWindowTitle(
|
||||
strings._("tag_browser_title") + " / " + strings._("manage_tags")
|
||||
)
|
||||
|
|
@ -38,9 +43,18 @@ class TagBrowserDialog(QDialog):
|
|||
layout.addWidget(instructions)
|
||||
|
||||
self.tree = QTreeWidget()
|
||||
if not self.cfg.documents:
|
||||
self.tree.setHeaderLabels(
|
||||
[strings._("tag"), strings._("color_hex"), strings._("date")]
|
||||
)
|
||||
else:
|
||||
self.tree.setHeaderLabels(
|
||||
[
|
||||
strings._("tag"),
|
||||
strings._("color_hex"),
|
||||
strings._("page_or_document"),
|
||||
]
|
||||
)
|
||||
self.tree.setColumnWidth(0, 200)
|
||||
self.tree.setColumnWidth(1, 100)
|
||||
self.tree.itemActivated.connect(self._on_item_activated)
|
||||
|
|
@ -119,6 +133,7 @@ class TagBrowserDialog(QDialog):
|
|||
|
||||
self.tree.addTopLevelItem(root)
|
||||
|
||||
# Pages with this tag
|
||||
pages = self._db.get_pages_for_tag(name)
|
||||
for date_iso, _content in pages:
|
||||
child = QTreeWidgetItem(["", "", date_iso])
|
||||
|
|
@ -127,6 +142,21 @@ class TagBrowserDialog(QDialog):
|
|||
)
|
||||
root.addChild(child)
|
||||
|
||||
# Documents with this tag
|
||||
if self.cfg.documents:
|
||||
docs = self._db.get_documents_for_tag(name)
|
||||
for doc_id, project_name, file_name in docs:
|
||||
label = file_name
|
||||
if project_name:
|
||||
label = f"{file_name} ({project_name})"
|
||||
child = QTreeWidgetItem(["", "", label])
|
||||
child.setData(
|
||||
0,
|
||||
Qt.ItemDataRole.UserRole,
|
||||
{"type": "document", "id": doc_id},
|
||||
)
|
||||
root.addChild(child)
|
||||
|
||||
if focus_tag and name.lower() == focus_tag.lower():
|
||||
focus_item = root
|
||||
|
||||
|
|
@ -153,12 +183,45 @@ class TagBrowserDialog(QDialog):
|
|||
def _on_item_activated(self, item: QTreeWidgetItem, column: int):
|
||||
data = item.data(0, Qt.ItemDataRole.UserRole)
|
||||
if isinstance(data, dict):
|
||||
if data.get("type") == "page":
|
||||
item_type = data.get("type")
|
||||
|
||||
if item_type == "page":
|
||||
date_iso = data.get("date")
|
||||
if date_iso:
|
||||
self.openDateRequested.emit(date_iso)
|
||||
self.accept()
|
||||
|
||||
elif item_type == "document":
|
||||
doc_id = data.get("id")
|
||||
if doc_id is not None:
|
||||
self._open_document(int(doc_id), str(data.get("file_name")))
|
||||
|
||||
def _open_document(self, doc_id: int, file_name: str) -> None:
|
||||
"""Open a tagged document via the default external application."""
|
||||
try:
|
||||
data = self._db.document_data(doc_id)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
strings._("project_documents_title"),
|
||||
strings._("documents_open_failed").format(error=str(e)),
|
||||
)
|
||||
return
|
||||
|
||||
suffix = Path(file_name).suffix or ""
|
||||
tmp = tempfile.NamedTemporaryFile(
|
||||
prefix="bouquin_doc_",
|
||||
suffix=suffix,
|
||||
delete=False,
|
||||
)
|
||||
try:
|
||||
tmp.write(data)
|
||||
tmp.flush()
|
||||
finally:
|
||||
tmp.close()
|
||||
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(tmp.name))
|
||||
|
||||
def _add_a_tag(self):
|
||||
"""Add a new tag"""
|
||||
|
||||
|
|
|
|||
|
|
@ -185,7 +185,12 @@ class TimeLogWidget(QFrame):
|
|||
return
|
||||
|
||||
dlg = TimeLogDialog(
|
||||
self._db, self._current_date, self, True, themes=self._themes
|
||||
self._db,
|
||||
self._current_date,
|
||||
self,
|
||||
True,
|
||||
themes=self._themes,
|
||||
close_after_add=True,
|
||||
)
|
||||
dlg.exec()
|
||||
|
||||
|
|
@ -214,6 +219,7 @@ class TimeLogDialog(QDialog):
|
|||
parent=None,
|
||||
log_entry_only: bool | None = False,
|
||||
themes: ThemeManager | None = None,
|
||||
close_after_add: bool | None = False,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self._db = db
|
||||
|
|
@ -224,6 +230,8 @@ class TimeLogDialog(QDialog):
|
|||
# programmatic item changes as user edits.
|
||||
self._reloading_entries: bool = False
|
||||
|
||||
self.close_after_add = close_after_add
|
||||
|
||||
self.setWindowTitle(strings._("time_log_for").format(date=date_iso))
|
||||
self.resize(900, 600)
|
||||
|
||||
|
|
@ -488,6 +496,8 @@ class TimeLogDialog(QDialog):
|
|||
)
|
||||
|
||||
self._reload_entries()
|
||||
if self.close_after_add:
|
||||
self.close()
|
||||
|
||||
def _on_row_selected(self) -> None:
|
||||
items = self.table.selectedItems()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class ToolBar(QToolBar):
|
|||
insertImageRequested = Signal()
|
||||
alarmRequested = Signal()
|
||||
timerRequested = Signal()
|
||||
documentsRequested = Signal()
|
||||
fontSizeLargerRequested = Signal()
|
||||
fontSizeSmallerRequested = Signal()
|
||||
|
||||
|
|
@ -120,6 +121,11 @@ class ToolBar(QToolBar):
|
|||
self.actTimer.setToolTip(strings._("toolbar_pomodoro_timer"))
|
||||
self.actTimer.triggered.connect(self.timerRequested)
|
||||
|
||||
# Documents
|
||||
self.actDocuments = QAction("📁", self)
|
||||
self.actDocuments.setToolTip(strings._("toolbar_documents"))
|
||||
self.actDocuments.triggered.connect(self.documentsRequested)
|
||||
|
||||
# Set exclusive buttons in QActionGroups
|
||||
self.grpHeadings = QActionGroup(self)
|
||||
self.grpHeadings.setExclusive(True)
|
||||
|
|
@ -159,6 +165,7 @@ class ToolBar(QToolBar):
|
|||
self.actInsertImg,
|
||||
self.actAlarm,
|
||||
self.actTimer,
|
||||
self.actDocuments,
|
||||
self.actHistory,
|
||||
]
|
||||
)
|
||||
|
|
@ -185,6 +192,7 @@ class ToolBar(QToolBar):
|
|||
self._style_letter_button(self.actCheckboxes, "☑")
|
||||
self._style_letter_button(self.actAlarm, "⏰")
|
||||
self._style_letter_button(self.actTimer, "⌛")
|
||||
self._style_letter_button(self.actDocuments, "📁")
|
||||
|
||||
# History
|
||||
self._style_letter_button(self.actHistory, "↺")
|
||||
|
|
|
|||
326
poetry.lock
generated
326
poetry.lock
generated
|
|
@ -267,13 +267,13 @@ xdg-desktop-portal = ["jeepney"]
|
|||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
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"},
|
||||
{file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"},
|
||||
{file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -380,57 +380,57 @@ tomli = {version = "*", markers = "python_version < \"3.11\""}
|
|||
|
||||
[[package]]
|
||||
name = "pyside6"
|
||||
version = "6.10.0"
|
||||
version = "6.10.1"
|
||||
description = "Python bindings for the Qt cross-platform application and UI framework"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
python-versions = "<3.15,>=3.9"
|
||||
files = [
|
||||
{file = "pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:c2cbc5dc2a164e3c7c51b3435e24203e90e5edd518c865466afccbd2e5872bb0"},
|
||||
{file = "pyside6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ae8c3c8339cd7c3c9faa7cc5c52670dcc8662ccf4b63a6fed61c6345b90c4c01"},
|
||||
{file = "pyside6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:9f402f883e640048fab246d36e298a5e16df9b18ba2e8c519877e472d3602820"},
|
||||
{file = "pyside6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:70a8bcc73ea8d6baab70bba311eac77b9a1d31f658d0b418e15eb6ea36c97e6f"},
|
||||
{file = "pyside6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:4b709bdeeb89d386059343a5a706fc185cee37b517bda44c7d6b64d5fdaf3339"},
|
||||
{file = "pyside6-6.10.1-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:d0e70dd0e126d01986f357c2a555722f9462cf8a942bf2ce180baf69f468e516"},
|
||||
{file = "pyside6-6.10.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4053bf51ba2c2cb20e1005edd469997976a02cec009f7c46356a0b65c137f1fa"},
|
||||
{file = "pyside6-6.10.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:7d3ca20a40139ca5324a7864f1d91cdf2ff237e11bd16354a42670f2a4eeb13c"},
|
||||
{file = "pyside6-6.10.1-cp39-abi3-win_amd64.whl", hash = "sha256:9f89ff994f774420eaa38cec6422fddd5356611d8481774820befd6f3bb84c9e"},
|
||||
{file = "pyside6-6.10.1-cp39-abi3-win_arm64.whl", hash = "sha256:9c5c1d94387d1a32a6fae25348097918ef413b87dfa3767c46f737c6d48ae437"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
PySide6_Addons = "6.10.0"
|
||||
PySide6_Essentials = "6.10.0"
|
||||
shiboken6 = "6.10.0"
|
||||
PySide6_Addons = "6.10.1"
|
||||
PySide6_Essentials = "6.10.1"
|
||||
shiboken6 = "6.10.1"
|
||||
|
||||
[[package]]
|
||||
name = "pyside6-addons"
|
||||
version = "6.10.0"
|
||||
version = "6.10.1"
|
||||
description = "Python bindings for the Qt cross-platform application and UI framework (Addons)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
python-versions = "<3.15,>=3.9"
|
||||
files = [
|
||||
{file = "pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:88e61e21ee4643cdd9efb39ec52f4dc1ac74c0b45c5b7fa453d03c094f0a8a5c"},
|
||||
{file = "pyside6_addons-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:08d4ed46c4c9a353a9eb84134678f8fdd4ce17fb8cce2b3686172a7575025464"},
|
||||
{file = "pyside6_addons-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:15d32229d681be0bba1b936c4a300da43d01e1917ada5b57f9e03a387c245ab0"},
|
||||
{file = "pyside6_addons-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:99d93a32c17c5f6d797c3b90dd58f2a8bae13abde81e85802c34ceafaee11859"},
|
||||
{file = "pyside6_addons-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:92536427413f3b6557cf53f1a515cd766725ee46a170aff57ad2ff1dfce0ffb1"},
|
||||
{file = "pyside6_addons-6.10.1-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:4d2b82bbf9b861134845803837011e5f9ac7d33661b216805273cf0c6d0f8e82"},
|
||||
{file = "pyside6_addons-6.10.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:330c229b58d30083a7b99ed22e118eb4f4126408429816a4044ccd0438ae81b4"},
|
||||
{file = "pyside6_addons-6.10.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:56864b5fecd6924187a2d0f7e98d968ed72b6cc267caa5b294cd7e88fff4e54c"},
|
||||
{file = "pyside6_addons-6.10.1-cp39-abi3-win_amd64.whl", hash = "sha256:b6e249d15407dd33d6a2ffabd9dc6d7a8ab8c95d05f16a71dad4d07781c76341"},
|
||||
{file = "pyside6_addons-6.10.1-cp39-abi3-win_arm64.whl", hash = "sha256:0de303c0447326cdc6c8be5ab066ef581e2d0baf22560c9362d41b8304fdf2db"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
PySide6_Essentials = "6.10.0"
|
||||
shiboken6 = "6.10.0"
|
||||
PySide6_Essentials = "6.10.1"
|
||||
shiboken6 = "6.10.1"
|
||||
|
||||
[[package]]
|
||||
name = "pyside6-essentials"
|
||||
version = "6.10.0"
|
||||
version = "6.10.1"
|
||||
description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
python-versions = "<3.15,>=3.9"
|
||||
files = [
|
||||
{file = "pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:003e871effe1f3e5b876bde715c15a780d876682005a6e989d89f48b8b93e93a"},
|
||||
{file = "pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1d5e013a8698e37ab8ef360e6960794eb5ef20832a8d562e649b8c5a0574b2d8"},
|
||||
{file = "pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b1dd0864f0577a448fb44426b91cafff7ee7cccd1782ba66491e1c668033f998"},
|
||||
{file = "pyside6_essentials-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:fc167eb211dd1580e20ba90d299e74898e7a5a1306d832421e879641fc03b6fe"},
|
||||
{file = "pyside6_essentials-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:6dd0936394cb14da2fd8e869899f5e0925a738b1c8d74c2f22503720ea363fb1"},
|
||||
{file = "pyside6_essentials-6.10.1-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:cd224aff3bb26ff1fca32c050e1c4d0bd9f951a96219d40d5f3d0128485b0bbe"},
|
||||
{file = "pyside6_essentials-6.10.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e9ccbfb58c03911a0bce1f2198605b02d4b5ca6276bfc0cbcf7c6f6393ffb856"},
|
||||
{file = "pyside6_essentials-6.10.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:ec8617c9b143b0c19ba1cc5a7e98c538e4143795480cb152aee47802c18dc5d2"},
|
||||
{file = "pyside6_essentials-6.10.1-cp39-abi3-win_amd64.whl", hash = "sha256:9555a48e8f0acf63fc6a23c250808db841b28a66ed6ad89ee0e4df7628752674"},
|
||||
{file = "pyside6_essentials-6.10.1-cp39-abi3-win_arm64.whl", hash = "sha256:4d1d248644f1778f8ddae5da714ca0f5a150a5e6f602af2765a7d21b876da05c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
shiboken6 = "6.10.0"
|
||||
shiboken6 = "6.10.1"
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
|
|
@ -534,147 +534,153 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
|||
|
||||
[[package]]
|
||||
name = "shiboken6"
|
||||
version = "6.10.0"
|
||||
version = "6.10.1"
|
||||
description = "Python/C++ bindings helper module"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
python-versions = "<3.15,>=3.9"
|
||||
files = [
|
||||
{file = "shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:7a5f5f400ebfb3a13616030815708289c2154e701a60b9db7833b843e0bee543"},
|
||||
{file = "shiboken6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e612734da515d683696980107cdc0396a3ae0f07b059f0f422ec8a2333810234"},
|
||||
{file = "shiboken6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b01377e68d14132360efb0f4b7233006d26aa8ae9bb50edf00960c2a5f52d148"},
|
||||
{file = "shiboken6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:0bc5631c1bf150cbef768a17f5f289aae1cb4db6c6b0c19b2421394e27783717"},
|
||||
{file = "shiboken6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:dfc4beab5fec7dbbebbb418f3bf99af865d6953aa0795435563d4cbb82093b61"},
|
||||
{file = "shiboken6-6.10.1-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:9f2990f5b61b0b68ecadcd896ab4441f2cb097eef7797ecc40584107d9850d71"},
|
||||
{file = "shiboken6-6.10.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4221a52dfb81f24a0d20cc4f8981cb6edd810d5a9fb28287ce10d342573a0e4"},
|
||||
{file = "shiboken6-6.10.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:c095b00f4d6bf578c0b2464bb4e264b351a99345374478570f69e2e679a2a1d0"},
|
||||
{file = "shiboken6-6.10.1-cp39-abi3-win_amd64.whl", hash = "sha256:c1601d3cda1fa32779b141663873741b54e797cb0328458d7466281f117b0a4e"},
|
||||
{file = "shiboken6-6.10.1-cp39-abi3-win_arm64.whl", hash = "sha256:5cf800917008587b551005a45add2d485cca66f5f7ecd5b320e9954e40448cc9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlcipher3-wheels"
|
||||
version = "0.5.5.post0"
|
||||
version = "0.5.6"
|
||||
description = "DB-API 2.0 interface for SQLCipher 3.x"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:836cff85673ab9bdfe0f3e2bc38aefddb5f3a4c0de397b92f83546bb94ea38aa"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3fde9076a8810d19044f65fdfeeee5a9d044176ce91adc2258c8b18cb945474"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ad3ccb27f3fc9260b1bcebfd33fc5af1c2a1bf6a50e8e1bf7991d492458b438"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94bb8ab8cf7ae3dc0d51dcb75bf242ae4bd2f18549bfc975fd696c181e9ea8ca"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0bf0a169b480615ea2021e7266e1154990762216d1fd8105b93d1fee336f49"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79cc1af145345e9bd625c961e4efc8fc6c6eefcaec90fbcf1c6b981492c08031"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d8b9f1c6d283acc5a0da16574c0f7690ba5b14cb5935f3078ccf8404a530075"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:952a23069a149a192a5eb8a9e552772b38c012825238175bc810f445a3aa8000"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24f1a57a4aa18d9ecd38cfce69dd06e58cfb521151a8316e18183e603e7108f4"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6568c64adc55f9882ba36c11a446810bd5d4c03796aab8ecb9024f3bca9eb2cd"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:26c2b58d2f2a9dd23ad4c310fb6c0f0c82ca4f36a0d4177a70f0efeb332798ee"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46827ffc7e705c5ecdf23ec69f56dd55b20857dc3c3c4893e360de8a38b4e708"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4033bbe2f0342936736ce7b8b2626f532509315576d5376764b410deae181cad"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-win32.whl", hash = "sha256:bfb26dbba945860427bd3f82c132e6d2ef409baa062d315b952dd5a930b25870"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-win_amd64.whl", hash = "sha256:168270b8fb295314aa4ee9f74435ceee42207bd16fe908f646a829b3a9daedad"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp310-cp310-win_arm64.whl", hash = "sha256:1f1bb2c4c6defa812eb0238055a283cf3c2f400e956a57c25cf65cbdbac6783f"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:55d557376a90f14baf0f35e917f8644c3a8cf48897947fcd7ecf51d490dd689f"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f1739264a971901088fe1670befb8a8a707543186c8eecc58158ca287e309b2"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:920e19345b6c5335b61e9fbed2625f96dbc3b0269ab5120baeae2c9288f0be01"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462734f6d0703f863f5968419d229de75bbf2a829f762bfb257b6df2355f977"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c397305b65612da76f254c692ff866571aa98fd3817ed0e40fce6d568d704966"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf3467fe834075b58215c50f9db7355ef86a73d256ac8ba5fffb8c946741a5dc"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a76d783c095a3c95185757c418e3bad3eab69cbf986970d422cce5431e84d7f5"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bf8d78895ee0f04dc525942a1f40796fa7c3d7d7fb36c987f55c243ce34192d"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d583a10dbe9a1752968788c2d6438461ec7068608ceaa72e6468d80727c3152e"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8c3156b39bb8f24dfbe17a49126d8fa404b00c01d7aa84e64a2293db1dae1a38"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:15c3cf77b31973aa008174518fa439d8620a093964c2d9edcb8d23d543345839"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f5743db0f3492359c2ab3a56b6bed00ecba193f2c75c74e8e3d78a45f1eb7c95"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc40a213a3633f19c96432304a16f0cff7c4aeca1a3d2042d4be36e576e64a70"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-win32.whl", hash = "sha256:433456ce962ae50887d6428d55bad46e5748a2cdd3d036180eb0bcdbe8bae9f9"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-win_amd64.whl", hash = "sha256:ca4332b1890cc4f80587be8bd529e20475bd3291e07f11115b1fc773947b264a"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp311-cp311-win_arm64.whl", hash = "sha256:a4634300cb2440baf17a78d6481d10902de4a2a6518f83a5ab2fe081e6b20b42"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc8df43c11d767c6aac5cc300c1957356a9fd1b25f1946891003cf20a0146241"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:797653f08ecffcef2948dfd907fb20dab402d9efde6217c96befafc236e96c5b"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dca428fde0d1a522473f766465324d6539d324f219f4f7c159a3eb0d4f9983c5"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e97922240a04b44637eabf39f86d243fe61fe7db1bd2ad219eb4053158f263"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8d3a366e52a6732b1ccff14f9ca77ecbee53abfce87c417bf05d4301484584f"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dce28a2431260251d7acf253ea1950983e48dfec64245126b39a770d5a88f507"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea64cce27152cae453c353038336bda0dc1f885e5e8e30b5cd28b8c9b498bbeb"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02d9e6120a496f083c525efc34408d4f2ca282da05bebcc967a0aa1e12a0d6ca"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:875cfc61bbf694b8327c2485e5ed40573e8b715f4e583502f12c51c8d5a92dd5"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0e9ed3ff9c00ba3888f8dbc0c7c84377ef66f21c5f4ac373fc690dcf5e9bd594"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:08ad6d767502429e497b6d03b5ae11e43e896d36f05ac8e60c12d8f124378bc1"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ecdefdcf7ab8cb14b3147a59af83e8e3e5e3bed46fc43ab86a657f5c306a83d2"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75ffe5677407bf20a32486eb6facfbb07a353ce7c9aecc9fefd4e9d3275605d7"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-win32.whl", hash = "sha256:97b6c6556b430b5c0dff53e8f709f90ba53294c2a3958a8c38f573c6dbf467d9"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-win_amd64.whl", hash = "sha256:248cae211991f1ffb3a885a1223e62abee70c6c208fc2224a8dbf73d4e825baa"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp312-cp312-win_arm64.whl", hash = "sha256:5a49fc3a859a53fd025dc2fa08410292d267018897fc63198de6c92860fa8be7"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2f798e1591fa5ba14d9da08a54f18e7000fd74973cde12eb862a3928a69b7996"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:369011b8dc68741313a8b77bb68a70b76052390eaf819e4cd6e13d0acbea602d"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5658990462a728c1f4b472d23c1f7f577eb2bced5bbbf7c2b45158b8340484bd"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:907a166e4e563da67fe22c480244459512e32d3e00853b3f1e6fdb9da6aa2da6"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ba972405e9f16042e37cbcb4fef47248339c8410847390d41268bd45dc3f6ca"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b5f1b380fe3b869f701f9d2a8c09e9edfeec261573c8bb009a3336717260d65"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2978c9d964ad643b0bc61e19d8d608a515ff270e9a2f1f6c5aeb8ad56255def"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29457feb1516a2542aa7676e6d03bf913191690bf1ed6c82353782a380388508"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:021af568414741a45bfca41d682da64916a7873498a31d896cc34ad540939c6b"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7471d12eef489eea60cc3806bae0690f6d0733f7aea371a3ad5c5642f3bc04a9"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0f5faf683db9ade192a870e28b1eeeec2eb0aeca18e88fa52195a4639974c7cb"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:64fe6bac67d2b807b91102eef41d7f389e008ded80575ba597b454e05f9522e5"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:63ef1e23eb9729e79783e2ab4b19f64276c155ba0a85ba1eeb21e248c6ce0956"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-win32.whl", hash = "sha256:4eafde00138dd3753085b4f5eab0811247207b699de862589f886a94ad3628a4"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-win_amd64.whl", hash = "sha256:909864f275460646d0bf5475dc42e9c2cadd79cd40805ea32fe9a69300595301"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp313-cp313-win_arm64.whl", hash = "sha256:a831846cc6b01d7f99576efbf797b61a269dffa6885f530b6957573ce1a24f10"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:aaad03f3eb099401306fead806908c85b923064a9da7a99c33a72c3b7c9143bf"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74d4b4cde184d9e354152fd1867bcbaee468529865703ad863840a0ce4eb60cd"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac3ad3cf9e1d0f08e8d22a65115368f2b22b9e96403fa644e146e1221c65c454"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45c4d3dca4acdbc5543bb00aee1e0715db797aa2819db5b7ca3feed3ab3366ff"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf7026851ea60d63c1a88f62439da78b68bfbfec192c781255e3cfb34b6efc12"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ae4a83678c41c2cdbf3c2b18fc46be32225260c7b4807087bdb43793ee90fa"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:678c67d60b35eced29777fe9398b6e6a6638156f143c80662a0c7c99ce115be7"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:9830af5aef2c17686d6e7c78c20b92c7b57c9d7921a03e4c549b48fe0e98c5c0"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:08651e17c868a6a22124b6ab71e939a5bb4737e0535f381ce35077dc8116c4b3"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:b58b4c944d7ce20cd7b426ae8834433b5b1152391960960b778b37803f0ffc1c"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2b38818468ddb0c8fc4b172031d65ced3be22ba82360c45909a0546b2857d3e4"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-win32.whl", hash = "sha256:91d1f2284d13b68f213d05b51cd6242f4cfa065d291af6f353f9cbedd28d8c0d"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:a5c724f95366ba7a2895147b0690609b6774384fa8621aa46a66cf332e4b612f"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e37b33263fad4accdba45c8566566d45fc01f47fd4afa3e265df9e0e3581d4f4"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f1e29495bc968e37352c315d23a38462d7e77fcfa1597d005d17ed93f9f3103"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad8a774f545eb5471587e0389fca4f855f36d58901c65547796d59fc13aee458"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75a5a0251e4ceca127b26d18f0965b7f3c820a2dd2c51c25015c819300fd5859"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:853e40535f0f57e8b605e1cbce1399c96bcd5ab99e60992d2c7669c689d0cbe5"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e04e1dd62d019cde936d18fcd21361f6c4695e0e73fd6dc509c4ccd9446d26d"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:2df377e3d04f5c427c9f79ef95bdf0b982bde76c1dbd4441f83268f3f1993a53"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:5f3cb8db19e7462ccb2e34b56feaccb2aac675ad8f77e28f8222b3e7c47d1f92"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:b40860b3e6d6108473836a29d3600e1b335192637e16e9421b43b58147ced3c1"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:ca4fd9e8b669e81bb74479bde61ee475d7a6832d667e2ce80e6136ddd7a0fedd"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:771e74a22f48c40b6402d0ca1d569ced5a796e118d4472da388744b5aa0ebd3f"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-win32.whl", hash = "sha256:4589bfca18ecf787598262327f7329fe1f4fc2655c04899d84451562e2099a57"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:f646ab958a601bad8925a876f5aa68bdf0ec3584630143ed1ad8e9df4e447044"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dfb8106a05af1cb1eadeea996171b52c80f18851972e49ffe91539e4fc064b0f"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b8b9b77a898b721fc634858fc43552119d3d303485adc6f28f3e76f028d5ea04"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c65efab1a0ab14f75314039694ac35d3181a5c8cf43584bd537b36caf2a6ccf9"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b450eee7e201c48aae58e2d45ef5d309a19cd49952cfb58d546fefbeef0a100"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8f0997202d7628c4312f0398122bdc5ada7fa79939d248652af40d9da689ef8"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dae69bef7628236d426e408fb14a40f0027bac1658a06efd29549b26ba369372"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef48e874bcc3ebf623672ec99f9aaa7b8a4f62fb270e33dad6db3739ea111086"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9006dc1a73e2b2a53421aa72decbcff08cb109f67a20f7d15a64ab140e0a1d2"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:96c07a345740fa71c0d8fc5fa7ea182ee24f62ebbf33d4d10c8c72d866dc332d"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:85a78af6f6e782e0df36f93c9e7c2dd568204f60a2ea55025c21d1837dea95ec"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:19eadc55bf69f9e9799a808cdcfc6657cf30511cb32849235d555adfa048a99f"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:377e8ad3bb3c17c43f860b570fd15e048246ade92babc9b310f2c417500aca57"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f8e07aec529d6fa31516201c524b0cfac108a9a6044a148f236291aae7991195"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-win32.whl", hash = "sha256:703ab55b77b1c1ebb80eb0b27574a8eadf109739d252de7f646dc41cb82f1a65"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp38-cp38-win_amd64.whl", hash = "sha256:b4f4b2e019c6d1ad33d5fc3163d31d0f731a073a5a52cdfae7b85408548ce342"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a84e3b098a29b8c298b01291cf8bc850a507ca45507d43674a84a8d33b7595b2"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9905b580cfdbd6945e44d81332483deace167d33e956ffae5c4b27eddeb676e7"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c403a7418631dc7185ef8053acc765101f4f64cc0bf50d1bc44ae7d40fc28e"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d63f89bf28de4ce82a7c324275ce733bf31eb29ec1121e48261af89b5b7f30b"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc6dc782f5be4883279079c79fa88578258a0fd24651f6d69b0f4be2716f7d7e"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743c177822c60e66c5c9579b4f384bd98e60fd4a2abe0eacdec0af4747d925bc"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5eeb87220e4d2abf6faad1ecb3b3ee88c4d9caad6cf2ce4c0a73a91c4c7ad9"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9318b814363b4bc062e54852ea62f58b69e7da9e51211afd6c55e9170e1ae9a0"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e7a1f58a2614f2ad9fcb4822f6da56313cbb88309880512bf5d01bd3d9142b87"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ae9427ddde670f605c84f704c12d840628467cc0f0a8e9ce6489577eef6a0479"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:be75ae311433254af3c6fe6eb68bf80ac6ef547ec0cf4594f5b301a044682186"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:73e1438437bbe67d453e2908b90b17b357a992a9ac0011ad20af1ea7c2b4cd58"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:393c0a9af080c1c1a0801cca9448eff3633dafc1a7934fdce58a8d1c15d8bd2b"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-win32.whl", hash = "sha256:13a79fc8e9b69bf6d70e7fa5a53bd42fab83dc3e6e93da7fa82871ec40874e43"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-win_amd64.whl", hash = "sha256:b1648708e5bf599b4455bf330faa4777c3963669acb2f3fa25240123a01f8b2b"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0-cp39-cp39-win_arm64.whl", hash = "sha256:4c3dd2f54bdd518b90e28b07c31cdfe34c8bd182c5107a30a9c2ef9569cd6cf9"},
|
||||
{file = "sqlcipher3_wheels-0.5.5.post0.tar.gz", hash = "sha256:2c291ba05fa3e57c9b4d407d2751aa69266b5372468e7402daaa312b251aca7f"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e16c8caf59e86589fb5f52253420db07121f1f96e2a12e244f6fdcaf8b946530"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:337f2e059114729dd1529ee356c98e2aa06440d6a9772917514a3bda0647c61c"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f6bd900658446e1cdeebda0760adb9a89f55888b460623db88b100845cb51bc2"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:dc6fcca569858145cb5ba3c878997d1788973e36f689090178f807b9a44d9ca6"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:eef50cc39554ad1fb82faa33d25c7f3cb11e2f7087b41109bc169db2c942f0c7"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:0fc36fc67f639a0e03cf6f7c6a5d1bc5cdd8005e8e07da3b21c54d4d81ed353b"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:53d0b861668d6847c7cc0dc7b443263b95a5cd211bcc326a457bd3122ebbb5a0"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:10aef293397a4ab25d8346ba5f96181214ab9c6a8836d83320cf23a2ad773a2c"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1105e7edba36a29625a824bff0eca3685c1cf6e391182b85a9a73b4b1604eef3"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5db9b4035e42a27672abbe75120908c74a235a496cd92b4c685fda1e95e9b19c"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f9e3fb5e96c5067a8cfd7b2fa7d939e529e30439058bbc15d0e9adca5e4cff1b"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6f3c1a8a4a2c04225f5159cf7f1c315101a89271afbaef4205c6fc50766c5535"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fc0504a1dbe6d478614ef55eb80d0c02ead24bc91f34b41c07d404452389f42d"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-win32.whl", hash = "sha256:05ef2b35f176e3b29092ec9aa03b09f4803feddbabdc2174e7ccc608758f2beb"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-win_amd64.whl", hash = "sha256:0f6873e4badf64eb8c5771c9e8a726df46ac663bc8051dfefb51fe2a46358b37"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp310-cp310-win_arm64.whl", hash = "sha256:9fd30c1cffa10f63f504a33494564efc0e0a475bbf069487016a9d2462d115e5"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a6c511bacd40ba769368b1abbf97fbefb285f525e6d2a399a704c22ba2aae37f"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa25610cda2b2a1b1cefddbd93488e939cf0059480f2fda5a8704acddd0e8935"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5a5258fb99e99b6fda6f011a0a4094ff99fe2e9b9ac7ce81cf646e0e779829a3"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:459836d52904fa006bf36e2144959bd21577c32947fdd173db50b037108a8620"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:5b36f9949f4d35c72f0626aaac109b17688c1d6a9a6e11de2538b4cfc32cfad0"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:87301b545556a1811780bb6fc6480ab1f2640d1d5b5e5e33ed404559ae383647"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:fcc4705b5b7bd3508d08a6389a45e14591071a3e575c2864c9c1c615df89e0da"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:0a231eb677a8246c47e423c710198631850c0a090e8f02a7fb1ad266ba517c56"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71ef871c65ad7c61048acb4f57da29bc0d5e35874183006222c229b5f1f64c73"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c3480298c9bc4117207535636fe74b01b4860ecd74a028c73b42f5f0ddaa8661"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d48cf218ed13f17e3037564f08fba7ddf2c260dac7993e3d4ac58ee30483f115"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ff57a80904b9bd55e18774cb59bffacad06e196298381ee576ce683d1c09b032"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:50978685717cd9293ff5508c192695a894879f9faed5142d0e8d7b63310f87c2"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-win32.whl", hash = "sha256:24207dbb699ca68fc5fc7248385fdf33a92fb1e17a6ea88d3cf2345a18fb29ff"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-win_amd64.whl", hash = "sha256:40b1f8188a0aa5bbec354a12561b014b43a6a0d0a0d230a8a9378ed7b826b0ec"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp311-cp311-win_arm64.whl", hash = "sha256:107ef02bbd0f2ffb39a564c14ebf3bedfa4569949a0d72ec8e106f754d715b7c"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:59a572b18d1ef8318e9f583a7b3e1a67b4b04ed4b783c3f29fa806635274d12a"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32dfb8903b24db5879b1f922114f650bc6a15df9d071c55eefeb6937e13b2d20"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f5770257736c43cbf910a22f74c1490ef1ecde0432e475904f038e64ffdacb0"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c33f99ddfe08c0f34807046800e510316b8bac2974b3c5fb9ecb1ee25c391ac8"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:97d4c000deeb72c2421f555f3e55a8c161ddfb0499caabf60df2bfde6460a5fc"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:67d9889028b4adfcaecd32e1e60330e1764c209ad12438f0eec2a5145ebf4a2d"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:00cf178b15da486ab43ee2bed41edb1b393c5cfe2a48cae68893a2b31260dbd3"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:95bfa4c5ffdd72d9d8676c913d585b7885a42824824cf1d9e93d3669f01492dd"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:030ab50a8f4153cfe8dd5c98724909b210243af2350b9c79914838905a99518e"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5dc3c3d9deea654f8ea9c1dbc7bc90561331e4da9c7055381fac6498ca7267a3"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cc986e8aa89e5a4a30b4eb8fd841d913a4e22ada99ec42be83f69bde3d86a31"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a41f0d30fa63d8db915566ec6987e68f064d96052cd6492ed8384b3e4807e60b"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f32fefe8a41e68334c545465813782fd45ef5cfe1082d012d95514c8a78e8015"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-win32.whl", hash = "sha256:ac2332f44758794a2fa19c77b824853e2a57ce5c27cc71c61066a52845be22d0"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-win_amd64.whl", hash = "sha256:6f016ba5a2a531938f332a234865dfc25d3a69abc169c3bf1d5c06c3c3f24601"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp312-cp312-win_arm64.whl", hash = "sha256:101ce0f7403801b6988d1f6c94244900e0f6c5378666e0ffd74b300687a6f9ef"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:94527fa3994c0fa1275c23d9fbb02512aacc675f1e45f566c660f4f9d5376e75"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0920a4b24362522ba83b36a47495d174221361213207191c325749a621fabeca"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5061b07b121ebd76aa697755b1b8f642cc3a27a0f6d392180ab249b35f1c2394"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:79de8511bb1fec62128e1b366cdc0cbd2ad1d725f3e29f9c91e96946a3c67945"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4b92c2f35bb8153cc20bcfc651536f51cc1194403782c542a852497ac789cbe2"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2d55211e3d2addff8a2df7335927d7fe6d75aa9ed12b396a22a5a0bfe2773ed9"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:8cb31de5d67799cc2bba92f23adc10281d66c2c16ca6418b94d80500a164aa60"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:123796de3e471db5ed8b4ee4f97ec562ad38347ad678dad71133eade280202e0"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6d34fabacfad4f301a22b5d8466d7ee3481f735bdb327d8756f04c81d3516c4"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:91b02fc765485c5b65f2a3eacfd2e16059253e007d0b5a5f24bba5fcea9032dd"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:13db7f23c553ffdd35f6e3b26415bdb9f100dcf89038873965caef769e8f1af5"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4ba79a81cd591d32a3a225e3e9b50a9871324d0e414fb6d0866049d8820e4e46"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f97be07997681ca90fb339d5411fcb957bd7cbe810389404baed207cb366badd"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-win32.whl", hash = "sha256:9e56e0a7aa778da3d46323fc1233da5dcede795a6c7fe4c11980fec0ce8c3fe3"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-win_amd64.whl", hash = "sha256:744845e4aa3cc614590f967aa1d38cc5d549177a2a83ed68c1821b5fb0505f8a"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp313-cp313-win_arm64.whl", hash = "sha256:c92de0b940533ca3a5b43a45d0768e0698b6ca95020b2fd47ec269b6bfc228d1"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a3f558df797aabf51680b3fbce48c4b3df89c36ad7fcaa3886b2ed8057aa2786"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7e216586720663960c82f046c495ef6d828e8e95c8fcf4c767b555fb9b8feead"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3e4ef70d3af8ebe6ababe8eff93b8bd4ad288d0a38ab29a2420c91d636fbfe14"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:11e34aac6cb7e29d23e339c5de9e87700ddf09886e104640578b5afb566a2c50"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:79e220312a075546e6be0a6062dda6315857b1478d78f97eb352f1383dde8ce2"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:b953af7b57867bcffeeab59681921671615ae4b42fd0a9234ad0be7e0e43dfd4"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:130ac318dbcb3a51a4377b0bf3e450c6c21d508a8b00d2d9d4b3ee6a46ab3595"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:5154c8022e58722987522ddce30f19fb69d6f8f6314959100d9f37c3dc5cba5b"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f91d1f5b7b927aa00a8d83724c58875d9d0e47bd81ca40445090ab521b5fa"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e1c140bfa6b0a7e08f414f2a9f8f529f7d8c4cfa8386ce588e6c747c4ccc6615"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:18fc56dfb32c6ce370d929897205027f78275c32446d6b1be712d462789ae8c2"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c03ec5e058fbf3fd94ecd8e0448834e8e7f46418eaec5fe5c7a0982c6e62c13f"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08756c1b25aebabb25a55dfe6f323876caea0c69511e34553807ae1d7ab843dd"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-win32.whl", hash = "sha256:bdbc58d224d27c002aed8a6361b43f3651943ecbfac69cd2674bbe681cf83790"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-win_amd64.whl", hash = "sha256:dcc313f4519922c1ec3406b010d53f700750c1cf5331b9633a3c8b196307e852"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314-win_arm64.whl", hash = "sha256:dc1f0c77cc0395680176913a1d634a4014a1ebf02e7a7b2ac03a180b44241842"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fc30e82d2b8f139ac1ab81a3b3d9a59da8e3ce3b1e753285727480667efd5417"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f11d1d2c41141dd95f7d45f03dbe9f69a6427463e69db50609d83c0cd29980b5"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:92beff11fd9683941de7b47b8fc280e834b135ba7966d139b0ce2159b551ebad"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3060403647df7d44844c2808a384e4c4cf4a2a1b65e509a8016aca971c08ad39"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:9380de7e8fc952f376c9dae9ba1cdbb6a24ff5e41fd8f3b3cf39f1e305ed3248"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:9a26be381b0fb1c8d4fcdfd48182c78217ae9458513e4fe51b5045d4f94d41cb"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-manylinux_2_28_s390x.whl", hash = "sha256:c3be08f8d81372a6d084062f969f88be0b942ac449b0ac01825b853c12705421"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c5bd4abbebc15f8a2a9a653500cd1abeb3aac13887fcc83de31ca40fce32e3a2"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4bb3c2c8e9a1e16455b989b2c7598b8053029bcbb519dc22601fa82bc8896f89"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:aac8ca9d2b4e18637e61ea1d8193500a1186f0b113b9224dc74186190f41c8e7"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3f237a41c3f08e69f2532aec29a2589097baa73886164537d90c744d3d2eb3b3"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:e6e59c3e0301cb04351b1cb12231aaadb40f56f779fb50a7857c6b4ed4c57297"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba2296a608081f4474f4447658a1e032d0b5506153baf68233471afde1463da9"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-win32.whl", hash = "sha256:8c8edfbd38a49ebbec2d1d56a000a499da2ac80b00488c156a1e0b8a7b8c10c6"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-win_amd64.whl", hash = "sha256:21df85bc14d5d86225c1e7466ff65cbcc10f0d1d4f466823b4534c4c0564554c"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp314-cp314t-win_arm64.whl", hash = "sha256:64df3e807fb0e6d89c1e90ce7c900bb82b695c474e1a0945a5f92862cac8b63d"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3784b22a29e4a675b456ca6ff1841d61e0eb97a28d0ba23d3d8cb5fe6da88238"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e27881be24f03d8a67a6db763f5671aaa05205de2380b1793b5e20bdabe49fba"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:667b6eec50ed03111676a0f4565be133643c9ad8bc88e6eea1c96b2af590c417"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4eaaa5cf77b125e05908b1200681e2988b1a6a307c7e677967053a1e4b07fba5"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-manylinux_2_28_i686.whl", hash = "sha256:4ead5b8f2607718548c8571e4a89fe735dd53443a2b5e42d8147eecd11b0d94b"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-manylinux_2_28_ppc64le.whl", hash = "sha256:d82a8a7b478d23368320ad185533d063ec14d11a1d188f07ace513a66bfa9580"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-manylinux_2_28_s390x.whl", hash = "sha256:39d871ee8c13d9b0326b24a02e5af21a7b1c8fb5e6f6f4ec62b935392202ec69"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:5a8737683621c2917a4ee9ff774e597a368c5b3d23f08ae53897d6bd1f8bfc0e"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:08b6922d5020384fa641c8dc416f6f2b143110c86dcf3aae086e7ce15b192eae"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f9dcc7f830ec56c090884a83be265c51c0a4fd60bb033b000c69c3bee08d77d8"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:0848f628b1528dd6a19a36679d8cde4b6f1f8d288757ba2e3df5578b79d79e90"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:1476bb15586ce27ea5fae7c54469b2be4efe51ca9cefa20871a6c394a18892cc"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:de17d373d9e7807236013950f598bf59b9ed7c375938fdb95378a7114e55ff95"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-win32.whl", hash = "sha256:02fa9e7f98a8e9be871219014b9ac015ba630b51615d90a2c06d45547a4b0cf1"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp38-cp38-win_amd64.whl", hash = "sha256:6b2d7daab225c578aec8109fde99624f281b4ccdc6c53c8cd8feb86d8e7d3cf2"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:abef5e28b4d1ca518291a8ca27af1cf9e4d68dd4a264d83874ec4d0a69589395"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd4c12a5a60cbd533ba4a3b4131d23302283ba597739c7867066b4efefe178db"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b7672837f1b9a6a67e375b743d74371d0428ead79ff367591145d06f3711c96"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:61c33e2697b0d91f3cbe806104e1d5b93961d3ab55ba55ee53bb36efe83c9933"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-manylinux_2_28_i686.whl", hash = "sha256:2e6eb09782dd719a1bb34af6e5ef25e5713c1f806231b472fcf64eb9288957af"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:6469b756ced0293e74806db2f114e5307cd4b05a559e986d3cc0b2eeb1eb8153"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:b6492f9bcb9296ac2179b5c9f7e7f329449b580836c0e8e5cfc2f3fe9af3486c"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2e4968d98917309463f02e4a48abebd95ed3d37968346f2693ed8a08e2fe9794"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:50214729697a1ee9e7603ba62b8ea46d78903ae1332caaa94fbaedde113944b7"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1ec9fd1dd5774d665903b8ba2e3e4f8ed72879dc42f6e9b2815040f0cb2d8ccd"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ced8ab30d205c8b6225b5703885576e629266767b091158731ec76c8c490bef4"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3c7242a267dd802fee273084a5707a95d02df4102afbea133c8f716234c7edcc"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a6c239d15085af4b0f3433fa274c1fc37369509b99a7c035a359d5142a0536d"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-win32.whl", hash = "sha256:cc29963df04a73d8420a4d023ba016c9013d86378969d8a11fe2148477282936"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-win_amd64.whl", hash = "sha256:38cc7bb3a371c4a5fe7f4236a409e64f1286796d780833243f9e15ef852f159d"},
|
||||
{file = "sqlcipher3_wheels-0.5.6-cp39-cp39-win_arm64.whl", hash = "sha256:186e49af3ddb98d260b95d436eaf58f2125712c268c8475627129c1f80a68164"},
|
||||
{file = "sqlcipher3_wheels-0.5.6.tar.gz", hash = "sha256:1d232c14be44db95a7f3018433cae01ecd18803fa2468fce3cc45ebd5e034942"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "bouquin"
|
||||
version = "0.5.5"
|
||||
version = "0.6.0"
|
||||
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
|
||||
authors = ["Miguel Jacq <mig@mig5.net>"]
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -61,8 +61,10 @@ def test_dates_with_content_and_search(fresh_db):
|
|||
assert _today() in dates and _yesterday() in dates and _tomorrow() in dates
|
||||
|
||||
hits = list(fresh_db.search_entries("alpha"))
|
||||
assert any(d == _today() for d, _ in hits)
|
||||
assert any(d == _tomorrow() for d, _ in hits)
|
||||
# search_entries now returns (kind, key, title, text, aux)
|
||||
page_dates = [key for (kind, key, _title, _text, _aux) in hits if kind == "page"]
|
||||
assert _today() in page_dates
|
||||
assert _tomorrow() in page_dates
|
||||
|
||||
|
||||
def test_get_all_entries_and_export(fresh_db, tmp_path):
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ def test_open_selected_with_data(qtbot, fresh_db):
|
|||
it = QListWidgetItem("dummy")
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
it.setData(Qt.ItemDataRole.UserRole, "1999-12-31")
|
||||
it.setData(
|
||||
Qt.ItemDataRole.UserRole,
|
||||
{"kind": "page", "date": "1999-12-31"},
|
||||
)
|
||||
s.results.addItem(it)
|
||||
s._open_selected(it)
|
||||
assert seen == ["1999-12-31"]
|
||||
|
|
@ -95,6 +98,6 @@ def test_populate_results_shows_both_ellipses(qtbot, fresh_db):
|
|||
qtbot.addWidget(s)
|
||||
s.show()
|
||||
long = "X" * 40 + "alpha" + "Y" * 40
|
||||
rows = [("2000-01-01", long)]
|
||||
rows = [("page", "2000-01-01", "2000-01-01", long, None)]
|
||||
s._populate_results("alpha", rows)
|
||||
assert s.results.count() >= 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue