#=============================================================================== # Define GUI popups, included in main.kv, used in main.py and other .py # # Popups are temporary modal overlays on whatever screen is current. # They prevent other actions from beign selected in the GUI but # do not prevent the GUI from responding to events (non-blocking). #=============================================================================== # for shared LabelTextDisplay: get in main.kv only, else warning ##:include common.kv #------------------------------------------------------------------------------ # INFO POPUP #------------------------------------------------------------------------------ : # Defined as a BoxLayout in main.py for properties. # General modal info popup: pass message, oncancel. # For all informational messages and interactions. # Toast alternative for PCs (all infos) and Android (long infos). # To see/augment this class in in .py, declare there or use Factory.name. size: root.size pos: root.pos orientation: 'vertical' # legacy PPUS notes... # because the TextInput itself would not scroll vertically; # text normally fits, and this uses default [do_wrap: True], # but add scrollview for large text or fontsize - textinput # won't vscroll by itself; scrolling is also much more likely # to be needed in landscape mode on phones; caveat: this code # is repeated redundantly: root.message won't work otherwise? ScrollView: # scrolled info text size_hint: (1.0, 1.0) id: textscroll do_scroll_y: True # auto opens at top of text effect_cls: 'ScrollEffect' # don't overscroll/snapback (class or str) LabelTextDisplay: text: root.message #on_ref_press: app.open_web_page(args[1]) # but [ref] not working in KivyMD: TBD DialogButtonsRows1: DialogButton: text: 'Okay' on_release: root.oncancel(root) # validate root=content instance #------------------------------------------------------------------------------ # CONFIRM POPUP #------------------------------------------------------------------------------ : # Defined as a BoxLayout in main.py for properties. # General modal verify popup: pass message, onyes, onno. # For confirming app exits, many search results, event-dialog closes. size: root.size pos: root.pos orientation: 'vertical' # see scrolling docs at InfoDialog above ScrollView: # scrolled confirm message size_hint: (1.0, 1.0) id: textscroll do_scroll_y: True # auto opens at top of text effect_cls: 'ScrollEffect' # don't overscroll/snapback LabelTextDisplay: text: root.message DialogButtonsRows1: # cancel/proceed buttons DialogButton: text: 'Cancel' # "No" and "Yes" aren't firm enough on_release: root.onno(root) # validate root=content instance DialogButton: text: 'Confirm' # was "Yes" but exits are perilous on_release: root.onyes(root) #------------------------------------------------------------------------------ # BUSY POPUP #------------------------------------------------------------------------------ : # Defined as a BoxLayout in main.py for properties. # General modal busy-wait popup: pass message (only). # For calendar-file loads and saves, threaded to avoid hangs: # - Starts with busy note, image animated, Okay disabled # - After op, caller changes note, stops animation, enables Okay # This both avoids a flash for fast actions and provides exit feedback. size: root.size pos: root.pos orientation: 'vertical' # see scrolling docs at InfoDialog above ScrollView: size_hint: (1.0, 1.0) # scrolled message id: textscroll do_scroll_y: True # auto opens at top of text effect_cls: 'ScrollEffect' # don't overscroll/snapback LabelTextDisplay: text: root.message # changed in .py on wait end Image: id: busyimage source: 'icons/Frigcal512-anim.gif' size: self.texture_size anim_loop: 0 # reps till stop, 0=keep looping #anim_delay: -1 # frames/sec, -1=stop, 0.1428=go (7/sec) DialogButtonsRows1: DialogButton: id: busyokay text: 'Okay' disabled: True # enabled by .py on thread exit on_release: root.onokay(root) # validate root=content instance #------------------------------------------------------------------------------ # NEW CALENDAR POPUP #------------------------------------------------------------------------------ : # Defined as a BoxLayout in main.py for properties. # User input of new calendar name: pass message, onadd, oncancel. # This is a main-menu action, uses this modal dialog (not a screen). size: root.size pos: root.pos orientation: 'vertical' # see scrolling docs at InfoDialog above ScrollView: size_hint: (1.0, 1.0) # scrolled user message id: textscroll do_scroll_y: True # auto opens at top of text effect_cls: 'ScrollEffect' # don't overscroll/snapback (class or str) LabelTextDisplay: text: root.message LabelTextDisplay: # spacer MDTextField: id: newcalname text: '' multiline: False mode: 'round' size_hint_x: 0.66 pos_hint: {'center_x': .50} # .padding fail: use spacers LabelTextDisplay: # spacer DialogButtonsRows1: DialogButton: text: 'Add' on_release: root.onadd(root) # validate root=content instance DialogButton: text: 'Cancel' on_release: root.oncancel(root) #------------------------------------------------------------------------------ # GO-TO-DATE POPUP #------------------------------------------------------------------------------ # UNUSED - replaced with a KivyMD 1.2.0 MDDatePicker, which has much # more functionality than pulldown m/d/y option menus... but also a # rigid dd/mm/yyyy bias, a weird "Wrong date" label glitch in input # mode, an arguably confusing year-choice UI, and a popup-only # packaging. But it's pretty and follows the Android motif. # KivyMD 2.x changed this radically and added an embeddable 'Docked' # variant that may be more consistent in this app, but it's far too # late to upgrade. KivyMD 1.2.0 is the default pip 'kivy' installed # today, 2.x is hugely backward incompatible, and the presence of both # lines' docs on the web is confusing at best. Churn breaks stuff. # : # # # Defined as a BoxLayout in main.py for properties # # Jump to Month+day of a date picked by user: pass message, ongoto, oncancel # # This is a main-menu action, uses this modal dialog (not a screen) # # # ...cut... #------------------------------------------------------------------------------ # EVENT-DIALOG POPUP: EDITS #------------------------------------------------------------------------------ : # Defined as a BoxLayout in monthgui.py for callbacks. # User view/edit/copy/delete of event details and opened # on event taps in both Month-screen days and events-list popup. id: dialogbox size: root.size pos: root.pos orientation: 'vertical' # Scroll entire inputs area, not just Note text, to both work around # a Kivy onscreen-keyboard overlay issue for the mutiline Note and # make Note visible in landscape mode on smaller phones. For more # on this scheme, see monthgui.py's now-defunct on_note_cursor_pos(). MDScrollView: id: notescroll do_scroll_y: True do_scroll_x: False effect_cls: 'ScrollEffect' # don't overscroll/snapback (class or str) #size_hint: (1, 1) #height: dialogbox.height - buttons.height MDBoxLayout: id: inputsbox orientation: 'vertical' size_hint_y: None height: self.minimum_height #height: max(self.minimum_height, notescroll.height) spacing: 16 # TBD: use dp()? padding: [dp(8), dp(8), dp(8), dp(8)] # [l, t, r, b] FormTextField: id: date hint_text: 'Date' readonly: True # prevent mods and keyboard, show in normal font is_focusable: False # prevent focus on taps (default=true on pcs ony) # but makes text color shaded light gray, unlike readonly # disabled: True # nope... # text_color_disabled: app.manual_theme_color_fg() # text_color_normal: app.manual_theme_color_fg() FormTextField: id: calendar hint_text: 'Calendar' readonly: True # not changeable here: use copy+delete+paste is_focusable: False # prevent focus on taps (pcs only?) FormTextField: id: category hint_text: 'Category' # changeable, default empty ok readonly: True # prevent on-screen keyboard on_touch_down: root.on_category_touched(args[1]) # popup menu, iff it's self # but not called on Android when readonly is True: bug! # on_focus: if self.focus: root.on_category_focus() # but focus stays on post close, and menu on_dismiss is uncalled: bug! # keyboard_mode: 'managed' # but this does not seems to have ever existed? (Google AI suggestion!) # use_softinput: False FormTextField: id: summary hint_text: 'Title' # shortened nicelabels for space (not ics attrs) # see notes below use_handles: app.editbubbles == 'Enabled' # select handles? use_bubble: app.editbubbles == 'Enabled' # select/longpress bubbles? FormTextField: id: description hint_text: 'Note' # shortened labels for space multiline: True # auto wraps on word boundaries # android only: selection widgets may be useful on touch # screens, but can be annoying and persistent otherwise, # and there is no clear way to to test for touch screens; # UPDATE: enable on PCs too because it's useful and not # all users will know about using control|command+A/C/V; # caveat: longpress selectall/paste bubble may post off- # screen for large notes, but select cut/copy/paste works; # UPDATE: selectall/paste bubble fixed with a workaround # subclass that forces it to appear near the touch cursor, # else it winds up posting off screen for larger notes; use_handles: app.editbubbles == 'Enabled' # select handles? use_bubble: app.editbubbles == 'Enabled' # select/longpress bubbles? # avoiding onscreen-keyboard overlay of newly added # multiline text is weirdly hard in kivy, despite a # softinput_mode='below'target' in main.py; this burned # days of tedious trial-and-error and in the end required # whole-dialog scrolling and a dummy under-keyboard widget; # else new text runs below widgets, and Android onscreen # keyboard overlays focused Note in full; see monthgui.py; # scrolling is still manual and subpar, but fixed Note size # that accommodates on-screen keyboard proved elusive (TBD); on_focus: root.on_note_focus(*args) # manage dummy widget # the trail of tears.... #size_hint_y: None #height: '20dp' #height: max(self.minimum_height, notescroll.height) #height: notescroll.height #height: self.minimum_height # or on_keyboard key == 13 or key == 0: # Enter key or touch down (Android) # but also an issue on pcs sans touch #on_cursor_pos: root.on_note_cursor_pos(*args) #height: max((len(self._lines) + 2) * self.line_height, notescroll.height) #height: max(self.minimum_height, notescroll.height) #max_height: max(self.minimum_height, notescroll.height) #height: self.minimum_height #max_height: root.height - (date.height + calendar.height + category.height + summary.height + buttons.height + (16 * 6)) #width: root.width # make this in the .py dynamically on focus/defocus # Label: # # dummy widget to avoid onscreen keyboard overlay of Note: hack + kivy bug! # size_hint_y: None # height: (Window.height / 2) - buttons.height DialogButtonsRows1: id: buttons padding: [0, dp(0), 0, 0] # dp(8) makes button bar shorter # TBD: or use Help/About button responsive sizing: # size_hint_y: dialogButtonRowHeight * 2 # no: use dp() scaled abs height everywhere to responsive size on PCs DialogButton: text: 'Update' on_release: root.on_event_update(root) # validate root=content instance DialogButton: text: 'Copy' on_release: root.on_event_copy() # copy for paste, don't close DialogButton: text: 'Delete' on_release: root.on_event_delete(root) # delete and close, post warn DialogButton: text: 'Cancel' on_release: root.on_event_cancel(root) # close after warn for changes #------------------------------------------------------------------------------ # EVENT-DIALOG POPUP: ADDS #------------------------------------------------------------------------------ : # Defined as a BoxLayout in monthgui.py for callbacks. # Used for input of event details and opened on tap of daynum # sans events, daynum-longpress paste, and event-list Add. # Same as edits, but Create+Cancel buttons and Calendar picker. # See EditEventDialog above for more notes on the UI structure here # NB: the .py coding assumes id names here are the same as in Edit. size: root.size pos: root.pos orientation: 'vertical' MDScrollView: id: notescroll do_scroll_y: True do_scroll_x: False effect_cls: 'ScrollEffect' # don't overscroll/snapback (class or str) MDBoxLayout: id: inputsbox orientation: 'vertical' size_hint_y: None height: self.minimum_height spacing: 16 # TBD: use dp()? padding: [dp(8), dp(8), dp(8), dp(8)] # [l, t, r, b] FormTextField: id: date hint_text: 'Date' readonly: True is_focusable: False # normal font, no mod or focus FormTextField: id: calendar hint_text: 'Calendar' # changable here, verify text readonly: True # prevent on-screen keyboard on_touch_down: root.on_calendar_touched(args[1]) # popup menu, iff it's self FormTextField: id: category hint_text: 'Category' # changeable, empty ok readonly: True # prevent on-screen keyboard on_touch_down: root.on_category_touched(args[1]) # popup menu, iff it's self FormTextField: id: summary hint_text: 'Title' # shortened labels for space # see notes at event-edit dialog use_handles: app.editbubbles == 'Enabled' # select handles? use_bubble: app.editbubbles == 'Enabled' # select/longpress bubbles? FormTextField: id: description hint_text: 'Note' # shortened labels for space multiline: True # auto wraps on word boundaries, supports \n # see notes at event-edit dialog use_handles: app.editbubbles == 'Enabled' # select handles? use_bubble: app.editbubbles == 'Enabled' # select/longpress bubbles? on_focus: root.on_note_focus(*args) # manage dummy widget # make dummy Label in the .py dynamically on focus/defocus DialogButtonsRows1: id: buttons padding: [0, dp(0), 0, 0] # use dp() scaled abs height everywhere to responsively size on PCs DialogButton: text: 'Create' on_release: root.on_event_create(root) # validate root=content instance DialogButton: text: 'Cancel' on_release: root.on_event_cancel(root) # close after warn for changes #------------------------------------------------------------------------------ # DAY'S EVENT LIST #------------------------------------------------------------------------------ : # Defined as a BoxLayout in monthgui.py for callbacks. # Used for user selection of events (a zoomed version # of day's grid for easier access) and opened on taps # of daynums that already have events. Includes an # Add for creating new events on days with events. size: root.size pos: root.pos orientation: 'vertical' spacing: 16 padding: [dp(8), dp(8), dp(8), 0] MDScrollView: id: eventsscroll size_hint: (1, 1) effect_cls: ScrollEffect # avoid snap-back animation throb (class or str) always_overscroll: False # Kivy scroll-defect workaround: monthgui.py MDGridLayout: id: eventsgrid cols: 1 # rest like monthgui.make_month_screen_widgets spacing: [0, dp(24)] # between-event tap space: [hor, ver] padding: (dp(4), dp(20), dp(4), 0) # (l, t, r, b), pad grid top else top event's line narrower size_hint_y: None # pad left/right to offset from dialog box border size_hint_x: None # enable scrolling: a kivy scourge height: self.minimum_height # bind in .kv instead of .py: not dynamic width: self.minimum_width # monthgui.py adds overlined and tappable event cells DialogButtonsRows1: DialogButton: text: 'Add' on_release: root.on_event_list_add() # validate .py self=content instance DialogButton: text: 'Cancel' on_release: root.on_event_list_cancel() #------------------------------------------------------------------------------ # COLOR POPUP #------------------------------------------------------------------------------ # TBD: is this, KivyMD's version, or a preset-colors list used? # Use preset primary colors/categories, but customs may be nice too # FINAL: this is unused in FC because only preset colors are allowed : # Defined as a BoxLayout in settingsgui.py for properties # Prebuilt content: pass oncancel, onpick # For Setting screen's calendar/category color selection size: root.size pos: root.pos orientation: 'vertical' ColorPicker: id: colorpicker DialogButtonsRows1: DialogButton: text: 'Cancel' on_release: root.oncancel(root) # validate root=content instance DialogButton: text: 'Pick' on_release: root.onpick(colorpicker.color, colorpicker.hex_color, root) #------------------------------------------------------------------------------ # (DEFUNCT) KIVY FILECHOOSER BUG WORKAROUND #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Redfine Kivy built-in widget's style to work arond an icon glitch. # # UNUSED IN FC: this is not required or used for KivyMD's MDFileManager. # Frigcal 4.0 uses the SAF chooser on Android and MDFileManager on PCs. # plyer and tkinter folder choosers work too, but plyer's is awful on # Windows (a blurry 1990s box), and tkinter runs independent of Kivy's # event loop (modal?) and has major issues on some macOS (see PyEdit). # Kivy's chooser is really a toolbox for building one; overkill in FC. # # In Kivy 2.1.0, the filechooser's icon view clips text vertically when # fonts are set larger than usual by the app or user settings. The fix # from PPUS tweaked code from PPUS's Kivy 2.1.0 that is identical in FC's # Kivy 2.3.1, but it's unclear if the redef is ignored by Kivy: a redef # warning is issued on app start, but is for PPUS and Kivy 2.1.0 too. In # any event, it's unused in FC: cut code, but save this stub as a reminder. #------------------------------------------------------------------------------ # [FileIconEntry@Widget]: # this syntax is also deprecated: use <>... # cut: see PPUS's .kv