From dcf522d825b2a69deef4d9e033b4826e81b375f8 Mon Sep 17 00:00:00 2001 From: Matt Lewis Date: Mon, 11 Sep 2023 23:27:27 +0200 Subject: [PATCH] Todo dialog. Added field for Notes (ical DESCRIPTION element). This has meant expanding the dialog, and so tabs have been added more advanced settings. --- docs/Usage.md | 4 +- docs/known_issues.md | 17 +- pygenda/glade/dialog_todo.glade | 190 +++++++++++++++++------ pygenda/locale/fr/LC_MESSAGES/pygenda.mo | Bin 9509 -> 9550 bytes pygenda/locale/fr/LC_MESSAGES/pygenda.po | 32 ++-- pygenda/pygenda_dialog_todo.py | 73 ++++++++- 6 files changed, 244 insertions(+), 72 deletions(-) diff --git a/docs/Usage.md b/docs/Usage.md index b85bc7e..511ae1a 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -49,8 +49,8 @@ Usage is intended to be intuitive, but a few things are worth noting: * Hint: To convert To-dos into Events, or vice-versa, cut & paste them. -* In the "Notes" field in the Event dialog, shift(or ctrl)+enter gives - a new line (just enter will confirm the dialog). +* In the "Notes" field in the Event and Todo dialogs, shift(or ctrl)+enter + gives a new line (just enter will confirm the dialog). * Setting alarms is currently under development and testing is welcome. (Note: sounding/displaying/sending alarms will be the job of a diff --git a/docs/known_issues.md b/docs/known_issues.md index 4408b2d..9d6c1f2 100644 --- a/docs/known_issues.md +++ b/docs/known_issues.md @@ -22,8 +22,8 @@ Major * Several event properties are not implemented (attendees, url, user tags, attachments, etc.) -* Many todo item properties are not implemented (details, start date, - user tags, timed-but-undated (use daily repeating?), etc.) +* Many todo item properties are not implemented (start date, user tags, + timed-but-undated (use daily repeating?), etc.) * Todo items can only be sorted by priority - add sort by duedate, by status, manual... @@ -217,9 +217,12 @@ Minor * In untranslated languages, there can be some awkward mixing of languages of fixed and generated content (e.g. "2. to last Sonntag of month") -* Newline behaviour in Notes field in Event dialog is not user-friendly. - Possible improvements: display a note in the field when it is empty; - a config option to swap with/without-modifier behaviours. +* Newline behaviour in the Notes field in Event and Todo dialogs is not + user-friendly. Possible improvements: display a note in the field when it + is empty; a config option to swap with/without-modifier behaviours. + +* There are accelerators to go to Time/Repeats/Alarm... subtabs for Event + editing. The same should exist for Todo editing. * In code, various places marked with '!!' indicating known bugs or temporary/placeholder implementations. @@ -251,8 +254,8 @@ Cosmetic * In Event dialog, Repeats tab, "Repeat until" label doesn't seem to align quite correctly with the field content. -* In Event dialog, in Notes field, the scroll-bar does not go to the border: - there is a 1-pixel gap. +* In Event and Todo dialogs, in Notes field, the scrollbar does not go to + the border: there is a 1-pixel gap. Testing ------- diff --git a/pygenda/glade/dialog_todo.glade b/pygenda/glade/dialog_todo.glade index 68b4566..94a7bf7 100644 --- a/pygenda/glade/dialog_todo.glade +++ b/pygenda/glade/dialog_todo.glade @@ -178,7 +178,7 @@ True False end - Due date: + Status: @@ -189,72 +189,168 @@ - + True False + start + 0 + + None + Action required + In progress + Completed + Cancelled + + + + 1 + 3 + + + + + True + True - + + + True + False + + + True + False + end + Due date: + + + + 0 + 0 + + + + + True + False + + + True + True + start + center + + + False + True + 0 + + + + + True + False + none + + + False + True + 1 + + + + + 1 + 0 + + + + + + True - True - start - center + False + Dates/Times - False - True - 0 + False - + + True False - none + + + True + False + end + start + Notes: + + + + 0 + 0 + + + + + True + True + True + True + never + always + in + False + False + + + True + True + word-char + False + + + + + + + + + 1 + 0 + + - False - True 1 - - - 1 - 3 - - - - - True - False - end - Status: - + + + True + False + Details + + + 1 + False + + 0 4 - - - - - True - False - start - 0 - - None - Action required - In progress - Completed - Cancelled - - - - 1 - 4 + 2 diff --git a/pygenda/locale/fr/LC_MESSAGES/pygenda.mo b/pygenda/locale/fr/LC_MESSAGES/pygenda.mo index 9bb050f14acb7ba6cf1b52a95b22688035e5a7f0..8ad92f62eb1920aa5a0793f1beb528f3c26e3933 100644 GIT binary patch delta 3501 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 fXk~|vU$qKP8I&-\n" "Language: fr\n" "MIME-Version: 1.0\n" @@ -78,7 +78,7 @@ msgstr "Date/heure de fin :" msgid "Duration:" msgstr "Durée :" -#: pygenda_dialog_entryprops.py:131 glade/dialog_todo.glade:181 +#: pygenda_dialog_entryprops.py:131 glade/dialog_todo.glade:224 msgid "Due date:" msgstr "Date limite :" @@ -170,11 +170,11 @@ msgid "Repeats:" msgstr "Répétition :" #: pygenda_dialog_entryprops.py:197 glade/dialog_event.glade:946 -#: glade/dialog_todo.glade:231 +#: glade/dialog_todo.glade:181 msgid "Status:" msgstr "Statut :" -#: pygenda_dialog_entryprops.py:217 pygenda_dialog_event.py:420 +#: pygenda_dialog_entryprops.py:217 pygenda_dialog_event.py:405 # Added by hand - not detected by gettext msgid "Audio" msgstr "Audio" @@ -304,7 +304,7 @@ msgstr "Modifier évènement" #: pygenda_dialog_event.py:1179 glade/dialog_event.glade:397 #: glade/dialog_event.glade:691 glade/dialog_event.glade:964 -#: glade/dialog_todo.glade:159 glade/dialog_todo.glade:248 +#: glade/dialog_todo.glade:159 glade/dialog_todo.glade:198 msgid "None" msgstr "Rien" @@ -328,11 +328,11 @@ msgstr "Resultats de recherche" msgid "No results found" msgstr "Pas d’entrées trouvées" -#: pygenda_dialog_todo.py:116 +#: pygenda_dialog_todo.py:143 msgid "New To-do" msgstr "Nouvelle tâche" -#: pygenda_dialog_todo.py:127 +#: pygenda_dialog_todo.py:154 msgid "Edit To-do" msgstr "Modifier tâche" @@ -527,7 +527,7 @@ msgstr "Lieu :" msgid "Tentative" msgstr "Provisoire" -#: glade/dialog_event.glade:966 glade/dialog_todo.glade:252 +#: glade/dialog_event.glade:966 glade/dialog_todo.glade:202 msgid "Cancelled" msgstr "Annulé" @@ -535,11 +535,11 @@ msgstr "Annulé" msgid "Confirmed" msgstr "Confirmé" -#: glade/dialog_event.glade:980 +#: glade/dialog_event.glade:980 glade/dialog_todo.glade:292 msgid "Notes:" msgstr "Notes :" -#: glade/dialog_event.glade:1030 +#: glade/dialog_event.glade:1030 glade/dialog_todo.glade:342 msgid "Details" msgstr "Détails" @@ -567,18 +567,22 @@ msgstr "1 (la plus haute)" msgid "9 (Lowest)" msgstr "9 (la plus basse)" -#: glade/dialog_todo.glade:249 +#: glade/dialog_todo.glade:199 msgid "Action required" msgstr "Action requise" -#: glade/dialog_todo.glade:250 +#: glade/dialog_todo.glade:200 msgid "In progress" msgstr "En cours" -#: glade/dialog_todo.glade:251 +#: glade/dialog_todo.glade:201 msgid "Completed" msgstr "Achevé" +#: glade/dialog_todo.glade:275 +msgid "Dates/Times" +msgstr "Dates/Heures" + #: glade/main.glade:53 msgid "_File" msgstr "_Fichier" diff --git a/pygenda/pygenda_dialog_todo.py b/pygenda/pygenda_dialog_todo.py index 404ad5d..c57a8e7 100644 --- a/pygenda/pygenda_dialog_todo.py +++ b/pygenda/pygenda_dialog_todo.py @@ -44,15 +44,23 @@ class TodoPropertyBeyondEditDialog(Exception): # Singleton class to manage Todo dialog +# !! There's overlap between this class and the EventDialogController class. +# !! Consider merging the shared elements. class TodoDialogController: dialog = None # type: Gtk.Dialog wid_desc = None # type: Gtk.Entry wid_todolist = None # type: Gtk.ComboBoxText wid_priority = None # type: Gtk.ComboBoxText + wid_status = None # type: Gtk.ComboBoxText + wid_tabs = None # type: Gtk.Notebook + wid_duedate_switch = None # type: Gtk.Switch revealer_duedate = None # type:Gtk.Revealer wid_duedate = None # type:WidgetDate - wid_status = None # type: Gtk.ComboBoxText + + buf_notes = None # type: Gtk.TextBuffer + buf_notes_scroller = None # type: Gtk.ScrolledWindow + list_default_cats = None # type: list @classmethod @@ -63,6 +71,14 @@ def init(cls) -> None: # Load glade file GUI.load_glade_file('dialog_todo.glade') + # Connect signal handlers + HANDLERS = { + 'notes_focusin': cls._notes_focus, + 'notes_focusout': cls._notes_focusloss, + 'notes_keypress': cls._notes_keypress, + } + GUI._builder.connect_signals(HANDLERS) + # Get some references to dialog elements in glade cls.dialog = GUI._builder.get_object('dialog_todo') if (not cls.dialog): # Sanity check @@ -73,7 +89,9 @@ def init(cls) -> None: cls._init_todolists() cls.wid_priority = GUI._builder.get_object('combo_todo_priority') cls.wid_status = GUI._builder.get_object('combo_todo_status') + cls.wid_tabs = GUI._builder.get_object('dialogtodo_tabs') cls._init_duedate() + cls._init_notes() @classmethod @@ -98,6 +116,15 @@ def _init_duedate(cls) -> None: cls.wid_duedate.get_style_context().add_class('hidden') # Remove -ve width warnings + @classmethod + def _init_notes(cls) -> None: + # Get references to notes widget. + # Called on app startup. + wid_notes = GUI._builder.get_object('textview_dialogtodo_notes') + cls.buf_notes = wid_notes.get_buffer() + cls.buf_notes_scroller = wid_notes.get_parent() + + @classmethod def _toggle_duedate(cls, wid:Gtk.ComboBox, val:bool) -> bool: # Callback for "Due date" switch. Shows or hides date field. @@ -139,6 +166,7 @@ def edit_todo(cls, todo:iTodo, list_idx:int=None) -> None: def _do_todo_dialog(cls, todo:iTodo=None, txt:str=None, list_idx:Optional[int]=0) -> Tuple[int,EntryInfo,int]: # Do the core work displaying todo dialog and extracting result. cls._seed_fields(todo=todo, txt=txt, list_idx=list_idx) + cls.wid_tabs.set_current_page(0) # first tab cls._reset_err_style() # clear error highlights discard_on_empty = (todo is None) # True if creating a new todo entry try: @@ -185,9 +213,10 @@ def _set_fields_to_defaults(cls) -> None: cls.wid_desc.grab_focus_without_selecting() cls.wid_todolist.set_active(0) cls.wid_priority.set_active(0) + cls.wid_status.set_active(0) cls.wid_duedate_switch.set_active(False) cls.wid_duedate.set_date(None) # today - cls.wid_status.set_active(0) + cls.buf_notes.set_text('') @classmethod @@ -230,6 +259,11 @@ def _set_fields_from_todo(cls, todo:iTodo) -> None: s = todo['STATUS'] if s in Calendar.STATUS_LIST_TODO: cls.wid_status.set_active_id(s) + if 'DESCRIPTION' in todo: + cls.buf_notes.set_text(todo['DESCRIPTION']) + # Move cursor & scollbar to start: + cls.buf_notes.place_cursor(cls.buf_notes.get_start_iter()) + cls.buf_notes_scroller.get_vadjustment().set_value(0) @classmethod @@ -296,4 +330,39 @@ def _get_entryinfo(cls) -> EntryInfo: if cls.wid_duedate_switch.get_active(): ei.set_duedate(cls.wid_duedate.get_date_or_none()) ei.set_priority(cls.wid_priority.get_active()) + ldesc = cls.buf_notes.get_text(cls.buf_notes.get_start_iter(), cls.buf_notes.get_end_iter(), False) + if ldesc: + ei.set_longdesc(ldesc) return ei + + + @classmethod + def _notes_focus(cls, entry:Gtk.Widget, ev:Gdk.EventFocus) -> bool: + # Handler for notes fields getting focus. + # Set style (on parent element to include scrollbar) + cls.buf_notes_scroller.get_style_context().add_class('focus') + return False # propagate event + + + @classmethod + def _notes_focusloss(cls, entry:Gtk.Widget, ev:Gdk.EventFocus) -> bool: + # Handler for notes field losing focus + cls.buf_notes_scroller.get_style_context().remove_class('focus') + return False # propagate event + + + @classmethod + def _notes_keypress(cls, wid:Gtk.Widget, ev:Gdk.EventKey) -> bool: + # Handler for key-press/repeat when notes field is focused + if ev.keyval == Gdk.KEY_Up: + # If cursor at start of text, need to handle navigation + cur = cls.buf_notes.get_property('cursor-position') + if cur==0: + cls.wid_tabs.grab_focus() + return True # Event handled, don't propagate + elif ev.keyval==Gdk.KEY_Return and ev.state&(Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK)==0: # not shift or control + # Manually trigger default event on dialog box + cls.dialog.response(Gtk.ResponseType.OK) + return True # Don't propagate event + + return False # Propagate event