!7 concept PasswordEntry { S purpose; SecretValue password; S globalID = aGlobalID(); } cmodule2 PasswordManager > DynCRUD { transient TrayIcon trayIcon; transient ReliableSingleThread rstMakeTrayIcon = dm_rst(this, r makeTrayIcon); start { db(); // TODO: move DB to JavaX-Secret onConceptsChangeAndNow(r { doLater(2.0, rstMakeTrayIcon) }); } void makeTrayIcon enter { disposeTrayIcon(trayIcon); new L menuItems; // no left-click action for (PasswordEntry e : conceptsSortedByFieldAlphanumIC(PasswordEntry, "purpose")) if (nemptyAfterTrim(getVar(e.password))) addAll(menuItems, "Copy password for " + e.purpose, rThread { copyPassword(e) }); add(menuItems, "---"); addAll(menuItems, "Edit Passwords...", rThread dm_activateOSAndModule); pcall-short { trayIcon = installTrayIcon(#1102954, "Password Manager", menuItems); } } afterVisualize { // TODO: disable when at beginning/end of list // TODO also: move to standard function addSelectionDependentButton("Move Up", r { PasswordEntry a = selected(), b = itemBefore(list(), a); if (a != null && b != null) swapConceptIDs(a, b); }); addSelectionDependentButton("Move Down", r { PasswordEntry a = selected(), b = itemAfter(list(), a); if (a != null && b != null) swapConceptIDs(a, b); }); addSelectionDependentButton("Copy password", rThread { copyPassword(selected()); }); } void cleanMeUp { disposeTrayIcon(trayIcon); } bool copyPassword(PasswordEntry p) { if (p == null) false; S pw = getVar(p.password); if (empty(pw)) ret false with infoBox("No password set for " + p.purpose); copyTextToClipboard(pw); ret true with infoBox("Password for " + p.purpose + " copied to clipboard"); } // API bool copyPasswordForGlobalID(S globalID) { PasswordEntry entry = conceptWhere PasswordEntry(+globalID); if (entry == null) false; ret true with copyPassword(entry); } }