Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions indicator-stickynotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '3.0')
gi.require_version('AppIndicator3', '0.1')
try:
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import AyatanaAppIndicator3 as appindicator
except (ValueError, ImportError):
gi.require_version('AppIndicator3', '0.1')
from gi.repository import AppIndicator3 as appindicator
from gi.repository import Gtk, Gdk
from gi.repository import AppIndicator3 as appindicator

import os.path
import locale
Expand Down Expand Up @@ -86,12 +90,12 @@ def __init__(self, args = None):
# Delete/modify the following file when distributing as a package
self.ind.set_icon_theme_path(os.path.abspath(os.path.join(
os.path.dirname(__file__), 'Icons')))
self.ind.set_icon("indicator-stickynotes-mono")
self.ind.set_icon_full("indicator-stickynotes-mono", "Sticky Notes")
self.ind.set_status(appindicator.IndicatorStatus.ACTIVE)
self.ind.set_title(_("Sticky Notes"))
# Create Menu
self.menu = Gtk.Menu()
self.mNewNote = Gtk.MenuItem(_("New Note"))
self.mNewNote = Gtk.MenuItem(label=_("New Note"))
self.menu.append(self.mNewNote)
self.mNewNote.connect("activate", self.new_note, None)
self.mNewNote.show()
Expand All @@ -100,12 +104,12 @@ def __init__(self, args = None):
self.menu.append(s)
s.show()

self.mShowAll = Gtk.MenuItem(_("Show All"))
self.mShowAll = Gtk.MenuItem(label=_("Show All"))
self.menu.append(self.mShowAll)
self.mShowAll.connect("activate", self.showall, None)
self.mShowAll.show()

self.mHideAll = Gtk.MenuItem(_("Hide All"))
self.mHideAll = Gtk.MenuItem(label=_("Hide All"))
self.menu.append(self.mHideAll)
self.mHideAll.connect("activate", self.hideall, None)
self.mHideAll.show()
Expand All @@ -114,12 +118,12 @@ def __init__(self, args = None):
self.menu.append(s)
s.show()

self.mLockAll = Gtk.MenuItem(_("Lock All"))
self.mLockAll = Gtk.MenuItem(label=_("Lock All"))
self.menu.append(self.mLockAll)
self.mLockAll.connect("activate", self.lockall, None)
self.mLockAll.show()

self.mUnlockAll = Gtk.MenuItem(_("Unlock All"))
self.mUnlockAll = Gtk.MenuItem(label=_("Unlock All"))
self.menu.append(self.mUnlockAll)
self.mUnlockAll.connect("activate", self.unlockall, None)
self.mUnlockAll.show()
Expand All @@ -128,12 +132,12 @@ def __init__(self, args = None):
self.menu.append(s)
s.show()

self.mExport = Gtk.MenuItem(_("Export Data"))
self.mExport = Gtk.MenuItem(label=_("Export Data"))
self.menu.append(self.mExport)
self.mExport.connect("activate", self.export_datafile, None)
self.mExport.show()

self.mImport = Gtk.MenuItem(_("Import Data"))
self.mImport = Gtk.MenuItem(label=_("Import Data"))
self.menu.append(self.mImport)
self.mImport.connect("activate", self.import_datafile, None)
self.mImport.show()
Expand All @@ -142,12 +146,21 @@ def __init__(self, args = None):
self.menu.append(s)
s.show()

self.mAbout = Gtk.MenuItem(_("About"))
self.mArchive = Gtk.MenuItem(label=_("Archive"))
self.menu.append(self.mArchive)
self.mArchive.connect("activate", self.show_archive, None)
self.mArchive.show()

s = Gtk.SeparatorMenuItem.new()
self.menu.append(s)
s.show()

self.mAbout = Gtk.MenuItem(label=_("About"))
self.menu.append(self.mAbout)
self.mAbout.connect("activate", self.show_about, None)
self.mAbout.show()

self.mSettings = Gtk.MenuItem(_("Settings"))
self.mSettings = Gtk.MenuItem(label=_("Settings"))
self.menu.append(self.mSettings)
self.mSettings.connect("activate", self.show_settings, None)
self.mSettings.show()
Expand All @@ -156,7 +169,7 @@ def __init__(self, args = None):
self.menu.append(s)
s.show()

self.mQuit = Gtk.MenuItem(_("Quit"))
self.mQuit = Gtk.MenuItem(label=_("Quit"))
self.menu.append(self.mQuit)
self.mQuit.connect("activate", Gtk.main_quit, None)
self.mQuit.show()
Expand Down Expand Up @@ -249,6 +262,10 @@ def show_about(self, *args):
def show_settings(self, *args):
wSettings = SettingsDialog(self.nset)

def show_archive(self, *args):
from stickynotes.gui import ArchiveDialog
ArchiveDialog(self.nset)

def save(self):
self.nset.save()

Expand Down
90 changes: 84 additions & 6 deletions stickynotes/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
# You should have received a copy of the GNU General Public License along with
# indicator-stickynotes. If not, see <http://www.gnu.org/licenses/>.

from datetime import datetime
from datetime import datetime, timedelta
import uuid
import json
from os.path import expanduser

from stickynotes.info import FALLBACK_PROPERTIES
from stickynotes.info import FALLBACK_PROPERTIES, DEFAULT_TRASH_RETENTION_DAYS, DEFAULT_CONFIRM_DELETE

class Note:
def __init__(self, content=None, gui_class=None, noteset=None,
Expand Down Expand Up @@ -60,9 +60,9 @@ def update(self,body=None):
self.last_modified = datetime.now()

def delete(self):
self.noteset.notes.remove(self)
"""Move note to archive instead of permanent deletion"""
self.noteset.archive_note(self)
self.noteset.save()
del self

def show(self, *args, **kwargs):
# If GUI has not been created, create it now
Expand Down Expand Up @@ -90,6 +90,7 @@ def cat_prop(self, prop):
class NoteSet:
def __init__(self, gui_class, data_file, indicator):
self.notes = []
self.archived_notes = [] # Archive for deleted notes
self.properties = {}
self.categories = {}
self.gui_class = gui_class
Expand All @@ -104,13 +105,26 @@ def loads(self, snoteset):
"""Loads notes into their respective objects"""
notes = self._loads_updater(json.loads(snoteset))
self.properties = notes.get("properties", {})
# Set default values for new properties
if "trash_retention_days" not in self.properties:
self.properties["trash_retention_days"] = DEFAULT_TRASH_RETENTION_DAYS
if "confirm_delete" not in self.properties:
self.properties["confirm_delete"] = DEFAULT_CONFIRM_DELETE
self.categories = notes.get("categories", {})
self.notes = [Note(note, gui_class=self.gui_class, noteset=self)
for note in notes.get("notes",[])]
# Load archived notes
self.archived_notes = notes.get("archived_notes", [])
# Clean up old archived notes
self.cleanup_old_archived_notes()

def dumps(self):
return json.dumps({"notes":[x.extract() for x in self.notes],
"properties": self.properties, "categories": self.categories})
return json.dumps({
"notes": [x.extract() for x in self.notes],
"archived_notes": self.archived_notes,
"properties": self.properties,
"categories": self.categories
})

def save(self, path=''):
output = self.dumps()
Expand Down Expand Up @@ -177,6 +191,70 @@ def hideall(self, *args):
for note in self.notes:
note.hide(*args)
self.properties["all_visible"] = False
def archive_note(self, note):
"""Move note to archive instead of permanent deletion"""
# Remove from active notes
if note in self.notes:
self.notes.remove(note)

# Extract note data and add deletion timestamp
archived_data = note.extract()
archived_data["deleted_at"] = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")

# Add to archived notes
self.archived_notes.append(archived_data)

# Hide GUI if exists
if note.gui:
note.gui.winMain.destroy()

def cleanup_old_archived_notes(self):
"""Remove archived notes older than retention period"""
retention_days = self.properties.get("trash_retention_days", DEFAULT_TRASH_RETENTION_DAYS)
if retention_days <= 0:
return # Keep notes forever if retention is 0 or negative

cutoff_date = datetime.now() - timedelta(days=retention_days)

# Filter out old archived notes
self.archived_notes = [
note for note in self.archived_notes
if datetime.strptime(note.get("deleted_at", "2000-01-01T00:00:00"),
"%Y-%m-%dT%H:%M:%S") > cutoff_date
]

def restore_note(self, archived_note_uuid):
"""Restore a note from archive"""
# Find the archived note
archived_note = None
for note in self.archived_notes:
if note.get("uuid") == archived_note_uuid:
archived_note = note
break

if not archived_note:
return None

# Remove from archive
self.archived_notes.remove(archived_note)

# Remove deleted_at timestamp
if "deleted_at" in archived_note:
del archived_note["deleted_at"]

# Create new note from archived data
restored_note = Note(archived_note, gui_class=self.gui_class, noteset=self)
self.notes.append(restored_note)

# Save changes
self.save()

return restored_note

def get_archived_notes(self):
"""Get list of archived notes with their metadata"""
return self.archived_notes


def get_category_property(self, cat, prop):
"""Get a property of a category or the default"""
Expand Down
Loading