File: PC-Phone USB Sync/code/PC-Phone USB Sync--source/pcphoneusbsync.kv
#------------------------------------------------------------------------------
# Part of PC-Phone USB Sync.
# Define the app's GUI, in a way that's integrated with the .py code.
#
# Copyright © 2023-2026 quixotely.com. All rights reserved.
# License: see ./main.py and ./terms-of-use.txt
#
# Disclaimer: like main.py, this file was not originally meant for
# publication, so please pardon any rough edges here. PPUS was a
# bit of a Kivy starter project, and it still shows in spots.
#
# OVERVIEW
#
# This file uses the Kivy language, which is supposed to separate layout
# from code and use a declarative style but seems a bit too magical
# at times. The relationship between code here and in the .py is not
# very well documented, if at all (hint: knowing how classes in the
# .py and .kv are added to the Factory helps; see Frigcal's code docs).
#
# Moreover, the Kivy language doesn't address parts that must still be
# built in the .py dynamically (e.g., the Main chooser's storage buttons
# and the entire month screen in Frigcal). And in hindsight, this code
# may be better manually loaded from a file and passed to the builder
# in the .py, to avoid an implicit naming convention and some thorny
# location issues for Kivy executables built with PyInstaller.
#
# On top of this, Kivy has missing bits (including a text widget that
# can handle non-trivial text and labels that don't require manual text
# splits to avoid blackouts); a non-orthogonal sizing/layout scheme
# that smacks too much of CSS (including the endless tweaking - and
# cursing); and a woeful lack of useful docs in general (expect to
# scour Kivy's code to grok even fundamental things). The sum makes
# Kivy seem more a GUI lib construction kit than a finished GUI lib.
#
# OTOH, Kivy does work with the appropriate amount of determination,
# as evidenced by this app and its Frigcal sibling at quixotely.com.
# But you should not expect it to be as friendly as Python's tkinter.
#
# For more Kivy editorial, see also the filechooser workaround ahead.
# It's a great system and very pliable because it's all Python/Cython
# down to the drawing, but it badly needs some finishing tweaks and docs.
# Though unused in this app (see Frigcal), KivyMD improves cosmetics,
# but it also adds a layer to the stack that changes more radically
# than it should and seems just as thinly documented as Kivy.
#
# Time will hopefully improve the Kivy/KivyMD story. Beeware/Toga still
# isn't there yet, and life is too short to use Java or Kotlin on Android.
#------------------------------------------------------------------------------
#:kivy 2.1.0
# for refs in py code here
#:import os os
#:import sys sys
#:import Clock kivy.clock.Clock
#:import Window kivy.core.window.Window
# [1.4] do in .py's on_width call back instead, for startup + rotations + multiwindow
# [1.4] now moot - display_cutout abandoned for insets callback, all done in .py
##:import get_height_of_bar android.display_cutout.get_height_of_bar
#------------------------------------------------------------------------------
# GLOBALS
#------------------------------------------------------------------------------
# for phone/pc-specific tweaks (see also top of main.py)
#:set onandroid hasattr(sys, 'getandroidapilevel')
#:set onmacos sys.platform.startswith('darwin')
#:set onwindows sys.platform.startswith('win')
#:set onlinux sys.platform.startswith('linux') and not onandroid # Linux ONLY
# [1.1] use dp(N) instead of N globally, both here and in the .py's code;
# this scales pixels (and widgets) to the host display's density, plus|minus;
# else buttons too small on hi-res displays, and too large on low-res displays;
# these may be overriden by size_hint_y %s, and/or sizing to self.texture_size;
# also use kivy.metrics.dp(N) instead of N in .py for dynamically built widgets;
# see also the related global font size's sp() scaling applied in the .py;
# all per https://kivy.org/doc/stable/api-kivy.metrics.html (see also cm());
# use density-scaled pixels, textheight no longer used [1.1]
#:set buttonheight dp(40)
#:set tallbuttonheight buttonheight
## absolute pixels: not ideal, but they work (if size_hint_y=None)
##:set textheight 90
##:set buttonheight 80
## phone increment; now moot [1.1]
##:set buttonheight (buttonheight + (4 if onandroid else 0))
## easier taps of crucials; now moot [1.1]
##:set tallbuttonheight (buttonheight + 16 if onandroid else buttonheight)
#------------------------------------------------------------------------------
# CLASSES
#------------------------------------------------------------------------------
# CAUTION: dynamic class names cannot be same as a Kivy class name.
# This is why an <ActionButton@Button> here silently never worked...
# Classes coded here and in the .py wind up in the Kivy Factory object.
# Folder paths, extended ahead
<PathDisplay@TextInput>:
multiline: False # one-line editable text field
foreground_color: 'black'
background_color: 'white'
# fix text clipping for higher user fontsize settings, and rightcrunch/curves
size_hint_y: None
height: self.minimum_height # lines height, includes padding
# [1.5] padding is weirdly brittle - don't change, else breaks valign of pathnames!
#padding: [16, 6, 16, 6] # [l, t, r, b], default [6]*4 (pixels)
padding: [dp(7), dp(3), dp(7), dp(3)] # scale to screen density, +t/b -l/r [1.1]
# [1.5] valign properly, per current size: Main tab more usable in slab landscape
padding_y: (self.height - self.line_height) / 2 # l+r still from padding (deprecated)
# [1.1] do grab+move scrolling on PCs (phones already do swipe scroll);
# holding longer selects text instead; a ScrollView might help too (tbd,
# but horizontal text scrolls are odd - see .py enable_logs_text_hscroll());
scroll_from_swipe: True # default is False for PCs, True for phones
# always show start of text on left edge of widget, until scrolled;
# now done for initial open in main.py's startup_gui_and_config_tweaks(),
# via text.cursor = (0, 0); halign and scroll_x here had no effect (why?);
#valign: 'middle' # moot? ([1.5]: yes, and nonexistent in TI - use padding_y)
#halign: 'left' # no effect?
#scroll_x: 0 # no effect?
# Multiple inheritance
# Yes, it works, and uses mro (not simple dflr) if it's mapped to Python classes (tbd)
<PathDisplayNoSelect@TextInputNoSelect+PathDisplay>
# Global fontsize for all widgets
# Yes, it works, if bound to app.dyn_font_size, not self|root (but KivyMD may override)
<Widget>:
font_size: app.dynamic_font_size # applied to every Widget subclass instance (!)
# Custom labels
<SectionLabel@Label>: # above sections in tabs
halign: 'center'
text_size: root.width, None # this is used, but may have no effect at all...
size: self.texture_size
color: 'cyan'
font_name: 'Roboto-BoldItalic' # for the kids
<LineLabel@Label>: # to the right of other widgets
text_size: self.size
halign: 'left'
size_hint_y: 1
valign: 'middle' # valign works on Label, but not TextInput
# padding: [32, 32] # [h, v] - but v made moot by size_hint_y!
padding: [dp(16), dp(16)] # scale to screen density [1.1]
# Color schemes - could be user configs someday (tbd, tmi?)
<PathButton@Button>
background_color: 'blue'
<ActivityButton@Button>
background_color: 'green'
<StorageButton@Button>
background_color: 'red' # not used here: made in .py
# [1.5] Redo dialog+tab footer buttons to have a uniform fixed size
# and not change with window resizes - else may become too small.
# This replaces a jumble of BoxLayouts that diverged over time.
# use density-scaled pixels for varying displays (defined above)
##:set buttonheight dp(40)
<DialogButtonsRows1@GridLayout>: # one-row button grid, fixed size
rows: 1
size_hint_y: None
height: buttonheight # and add N DialogButton, cols = N
<DialogButtonsRows2@GridLayout>: # two-row button grid, fixed size
rows: 2
size_hint_y: None
height: buttonheight * 2 # and add N DialogButton, cols = ceil(N / 2)
<DialogButton@Button>
background_color: 'navy'
# [1.5] Enable markup in text displays, and split large text across N
# labels for Help and About - a workaround for Label text-size limits,
# and required to avoid blank/blacked-out labels for nontrivial text.
# Unlike KivyMD, Kivy Label has no background color sans Canvas ops.
# Text requires massive amounts of memory: see Recycleview fail ahead.
<SplitTextBoxLayout@BoxLayout>:
# [1.5] Custom container for Label parts of Help and About screens.
# The .py adds N LabelTextDisplay instances based on text size.
orientation: 'vertical' # stack children on top of each other
size_hint_y: None # plain Kivy (not KivyMD) version
height: self.minimum_height
#adaptive_height: True # KivyMD: adjust height per childrens' content
<BackgroundColor@Widget>:
# [1.5] UNUSED experiment that was abandoned before it worked.
# Set backround_color and color as construction arguments.
# Mix in to another widget class to enable bg colorization;
# canvas.before ops are run before widget is drawn on canvas.
# PUNT: did not work as coded, no time for struggles, and the
# Kivy default dark-gray background is better for consistency.
background_color: 1, 1, 1, 1 # default if not overridden by subclass
canvas.before:
Color:
rgba: root.background_color
Rectangle:
size: self.size
pos: self.pos
<LabelTextDisplay@Label>: # punt: "+BackgroundColor>:"
# [1.5] For showing text in labels with Kivy markup. This is used as
# is in dialogs, and larger Help/About text is spread across N of these
# in the .py, else nontrivially sized text may be blacked out (empty).
# Kivy/KivyMD TextInput/Field handle larger text, but have no markup.
# KivyMD: must bind font_size after create in .py else auto-set per MD.
# Now also used for dialogs like info_message() to enable colors and
# custom [H] headers implemented by this app. This sets bg to default
# gray, and text is not split for dialogs: assumed to be short enough.
# This replaces prior highly-customized TextInputNoSelect appearances.
# Note that Kivy Label wraps if text_size or embedded \n characters; per
# its code, KivyMD MDLabel autowraps if no adaptive_size or adaptive_height.
# NIT: Label uses different line-wrap rules than Text - it splits on just
# spaces and not periods. This works fine for prose but can be subpar for
# pathnames. Label splits use split_str in kivy.core.text's LabelBase,
# which defaults to ' ' and can be changed, but Text doesn't split on / or
# \ either and uses . unevenly, and this is a substantial can of worms...
markup: True # use '[]' for bold, italic, under, color, etc.
text_size: self.width, None # enables word wrapping
size_hint_y: None # use plain kivy version, no kivymd adaptive_*
size: self.texture_size # no min_height (and why is sizing so wonky?)
halign: 'left'
padding: [dp(10), 0] # just [h, v] in Kivy 2.1.0, not [l, t, r, b], was abs 8
# these work only in Text... # harmless but pointless in Label: kivy silently ignores!
# multiline: True # for Text, enable N line plus line wraps on words
# do_wrap: True # tip: Label also splits text on embedded \n
# oddments...
# allow_copy: False # irrelevant for Label, for TextInput = no edit bubbles
# theme_text_color: 'Primary' # kivymd only: also foreground_color, background_color
# the usual sizing song and dance...
# adaptive_height: True # kivymd only: enables scrolling = [size_hint_y: None] + [height: self.minimum_height]
# size_hint: 1, 1 # relative to parent size: fill available space, default
# size_hint: (None, None) # enable provided size (and its many variants)
# height: self.minimum_height # for TextInput, not label (inconsistently)
# height: self.texture_size[1] # non-orthogonal like CSS (same persistence required)
# CATCH: Kivy 2.1.0 allows just two values for Label padding, [h, v], so we
# must handle top xor bottom padding specially. One-label cases (dialogs)
# can pad both, but N-label split-text cases (Help/About) must add spacers
# above and below the labels container. Kivy 2.3.1 allows [l, t, r, b]
# for padding, which allows Frigcal to handle Help/About padding in .py
# code, but 2.3.1 is too risky to use for PPUS given PPUS's many brittle
# workarounds for Kivy bugs: disable in the .py, and pad here instead.
# See CATCH in the .py's set_split_label_text() for expanded coverage.
# Help/About
<VerticalSpacer10@Label>:
size_hint_y: None
height: dp(10) # vertical spacer, 10 density-scaled pixels high
# dialogs
<SpacedLabelTextDisplay@LabelTextDisplay>:
padding_y: dp(10) # pad both top and bottom in kivy 2.1.0
padding_x: dp(8.0) # match dialog header's indent, dialog already padded
# Now defined in .py for conditional color
# <ConfigCheckbox@CheckBox>
# # [r, g, b, a] for image tinting - too dim on macos only
# color: [1, 3, 5, 4] if sys.platform.startswith('darwin') else [1, 1, 1, 1]
# color: self.check_box_color # handle in .py, auto-update on changes
#------------------------------------------------------------------------------
# ROOT WIDGET
#------------------------------------------------------------------------------
Main:
# Root widget (instance), maps to .py's class Main via Factory
# available in .py
# run_output: runoutput
# ref as this or self.ids.<id> or self.ids['id']
from_Path: frompath
to_path: topath
# [1.4] negate android 15 edge-to-edge display by padding window for insets
# now done in .py's on_width calback here for startup + roatations + multiwindow
# padding: 0, get_height_of_bar('status'), 0, get_height_of_bar('navigation');
# PUNT: failed for small-screen landscape where navbar is on left or right;
# see .py for later insets-based solution that is a more complete solution;
##on_width: root.neuter_android15_edge_to_edge()
##on_width: root.force_redraw()
TabbedPanel:
# size_hint: .5, .5
# pos_hint: {'center_x': .5, 'center_y': .5}
id: toptabs
do_default_tab: False
on_current_tab: root.on_tab_switch()
# Failed [1.1]: force redraw for Android phone rotations;
# neither this nor root.canvas.ask_update() worked; may be
# an sdl2 glitch: see docs in .py enable_logs_text_hscroll()
# and later on_start() tries - display size events are being
# dropped by SDL2 or Kivy, or botched by Samsung Android.
#
# on_width:
# (print('redraw'),
# (Clock.schedule_once(lambda dt: (print('layout'), self._trigger_layout())), 1.0)
# if onandroid else None)
#------------------------------------------------------------------------------
# MAIN TAB
#------------------------------------------------------------------------------
TabbedPanelItem:
id: maintab
text: 'Main'
BoxLayout: # layout container required in each tab
orientation: 'vertical'
SectionLabel:
size_hint_y: .10
text: 'Choose Content Folders'
GridLayout:
cols: 2
size_hint_y: .15
# cols_minimum: {0: dp(20)}
# padding: [10, 0, 10, 0] # [l, t, r, b]
padding: [dp(0), 0, dp(5), 0] # drop pads, scale to density [1.1]
PathButton:
size_hint_x: 0.40
text: 'FROM'
on_release: root.do_main_path('FROM', 'frompath')
PathDisplay:
size_hint_y: 1
id: frompath
text: 'This comes from settings default or save...'
font_name: 'DejaVuSans'
PathButton:
size_hint_x: 0.40
text: 'TO'
on_release: root.do_main_path('TO', 'topath')
PathDisplay:
size_hint_y: 1
id: topath
text: 'This comes from settings default or save...'
font_name: 'DejaVuSans'
on_text:
# [1.1] Never reenable UNDO if any action is in
# progress, else user could launch a parallel UNDO!
# But clear undo_verboten so enabled on script exit,
# and enable UNDO now iff no action running now.
# This fires for both manual and chooser TO mods.
root.undo_verboten = False;
if not root.script_running: undobutton.disabled = False
SectionLabel:
size_hint_y: 0.10
text: 'Start Action'
GridLayout:
cols: 2
size_hint_y: 0.50
# cols_minimum: {0: 0.25, 1: 0.75}
# padding: [10, 0, 10, 0] # [l, t, r, b]
padding: [dp(0), 0, dp(0), 0] # drop pads, scale to density [1.1]
ActivityButton:
size_hint_x: 0.40
id: syncbutton
text: 'SYNC'
on_release: root.do_sync(frompath.text, topath.text)
LineLabel:
text: 'Make TO the same as FROM'
# text: 'Propagate changes in FROM to TO'
ActivityButton:
size_hint_x: 0.40
id: showbutton
text: 'SHOW'
on_release: root.do_show(frompath.text, topath.text)
LineLabel:
text: 'Report FROM/TO differences only'
ActivityButton:
size_hint_x: 0.40
id: undobutton
text: 'UNDO'
on_release: root.do_undo(topath.text)
LineLabel:
text: 'Roll back TO\'s most recent SYNC'
ActivityButton:
size_hint_x: 0.40
id: copybutton
text: 'COPY'
on_release: root.do_copy(frompath.text, topath.text)
LineLabel:
text: 'Make a full copy of FROM in TO'
ActivityButton:
size_hint_x: 0.40
id: diffbutton
text: 'DIFF'
on_release: root.do_diff(frompath.text, topath.text)
LineLabel:
text: 'Compare FROM to TO byte for byte'
ActivityButton:
size_hint_x: 0.40
id: namebutton
text: 'NAME'
on_release: root.do_name(frompath.text)
LineLabel:
id: namelabel
text: 'Make filenames portable in FROM'
SectionLabel:
size_hint_y: 0.10
id: statuslabel
text: 'Action Status'
Image:
size_hint_y: 0.20
id: statusimg
source: 'usbsync-anim.gif'
anim_delay: -1 # frames/sec, -1=stop, 0.20=go
anim_loop: 0 # reps till stop, 0=keep looping
#------------------------------------------------------------------------------
# LOGS TAB
#------------------------------------------------------------------------------
TabbedPanelItem:
text: 'Logs'
id: logstab
BoxLayout:
orientation: 'vertical'
SectionLabel:
id: logslabel
text: 'View Run Logfiles'
#size_hint_y: 0.05 # [1.5] size+padding for splitter
size_hint_y: None
size: self.texture_size
padding_y: dp(7)
Label:
id: logspath
text: root.logfile_path
text_size: root.width, None
halign: 'center'
#size_hint_y: 0.05 # [1.5] size+padding for splitter
size_hint_y: None
size: self.texture_size
padding_y: dp(6)
# else small/big display/font wraps to a line 2, which is
# partly vclipped no reason to scroll this: rarely sortened,
# it's info only (+in docs), and can open|explore to check;
# this shortens on 'right', because it's a path, and always
# ends in long appname; could hack Main's chooser popup to do
# same in list view, but its 'center' is useful for basenames;
shorten: True
shorten_from: 'right' # default 'center' obscures worse
# using a kivy filechooser here was a major pain in the app:
# autoselect was elusive, prior selections were not cleared after
# _update_files() (autoselect or not), icon-view didn't show full
# filename, and popup requires pointless taps; punt and go custom
AnchorLayout:
size_hint_y: 0.30
anchor_x: 'center' # center scroll+filelist in window
ScrollView:
# pos_hint: {'center_x': .5}
# width: picklogitems.width
size_hint_x: None # which is like (None, 1)
id: picklogscroll
do_scroll_x: True
do_scroll_y: True
effect_cls: 'ScrollEffect' # don't overscroll/snapback
# one of the more subtle bits here: this ensures that
# the filelist scrolls horizontally if the window is too
# narrow to display it, by listening for changes in the
# root's width; this also ensures hscrolls if the filelist
# doesn't fit the window due to large fontsize settings (on
# Apply, startup, or restore), because it's listening to both
# window size and filelist size; the filelist normally fits
# the display and the scrollbar is set the same width - this
# is just for pathologically narrow displays or large fonts;
width: min(picklogitems.minimum_width, root.width)
BoxLayout:
id: picklogitems
orientation: 'vertical' # was grid - cols: 1
# use less padding on PCs, to match phones [1.1]
spacing:
# scale to density, not platform [1.1]
dp(2.5)
# an initial resizing, superseded [1.1]
# (8 if onandroid
# else (5 if onmacos else 4)) # was grid - spacing: (0, 8) # (h, v)
# do in .py for dynamic creation
# size_hint: (None, None)
# width: self.minimum_width
# height: self.minimum_height
# [1.5] Make Logs' middle green action-buttons row fixed sized too?
# PROS: avoid growth as window expands so add as much log-viewer space
# as possible. Won't shrink on window contract, but too small unusable.
#
# CONS: this reveals only another line or two, because the logfiles
# list at the top also expands; it differs from the Main and Config
# tabs, whose buttons do grow+shrink with window; and Android landscape
# and split/popup multi-window modes argue for allowing row to shrink.
# Logs' logfiles-list cells do not grow+shrink, but they're unmoddable.
#
# USED: this became fixed in the end, for the new Splitter - see below.
# ALSO: changed WATCH to trigger by on_press instead of on_release, else
# no callback sent and button is stuck on and requirestap to clear if
# swipe over button - which happened in 1.4 too but is much more likely
# in 1.5 because the slider is just below WATCH. Other buttons still
# require a release over the button to trigger: okay - not toggles.
#BoxLayout:
# size_hint_y: 0.08
# orientation: 'horizontal'
DialogButtonsRows1: # [1.5] fixed-size row, not % relative (for splitter)
id: logsactionsrow
ActivityButton:
text: 'TAIL'
id: logfiletail
on_release: root.do_logs_tail(picklogitems)
ToggleButton:
text: 'WATCH'
id: logfilewatch
background_color: 'green'
on_press: root.do_logs_watch(picklogitems) # [1.5] not on_release, else stuck on if swipe
ActivityButton:
text: 'OPEN'
id: logfileopen
on_release: root.do_logs_open(picklogitems)
ActivityButton:
text: 'EXPLORE'
id: logfileexplore
on_release: root.do_logs_explore()
# [1.5] Wrap file-view area in a Splitter so users can resize it.
# This may avoid the extra steps of OPEN in some cases, and allows
# more filenames to be viewed without scrolling. Both file-view and
# filename areas still scroll as before, but the green action-buttons
# row is now fixed size so only the other two respond to the splitter,
# and space has been hardcoded around the top label and filepath.
# To avoid running widgets over top tabs or cruching the label/path,
# must calculate and limit max_size of file-view area from Window.
# On Android, max_size also requires deducting edge-to-edge padding;
# on Windows+Linux, it uses less fudge so it fully covers files list
# app.android_e2e_window_height_pad: property, so mods update max_size;
# Can split long property values with indent + '\', despite Kivy docs!
# Python (a if x else b if y else c) == (a if x else (b if y else c)).
# The splitter bar supports both drags and double taps for resizes.
# Its default pt(10) (a.k.a. '10pt' in the source) was nearly unusable
# on Linux (only), and just a hair too narrow on Windows touch screens.
Splitter:
id: logssplitter
sizable_from: 'top' # resizable from the top side of this widget
# minimum height: smallest that logview can be at display bottom
min_size: dp(96)
# maximum height: else top tabs overlayed or label+path crunched
#max_size: Window.height * .80
# more accurate: stop at bottom of label+path, deducts e-2-e padding and fudge
max_size:
Window.height - \
(toptabs.tab_height + \
logsactionsrow.height + logslabel.height + logspath.height + \
app.android_e2e_window_height_pad + \
(8 if onwindows else 6 if onlinux else 12))
size_hint_y: 0.50 # initial percent of window
rescale_with_parent: True # retain parent % when parent resized (moot w/max?)
# size of the divider bar itself, defaults to '10pt' (i.e., pt(10))
strip_size:
pt(14) if onlinux else \
pt(10) if onandroid else \
pt(12) # default: Linux unusable, Windows+macOS narrowish, Android ok
ScrollView: # only one child allowed in splitter
# because TextInput itself would not scroll horizontally
id: logfilescroll
#size_hint_y: 0.50
do_scroll_x: True
do_scroll_y: True
effect_cls: 'ScrollEffect' # don't overscroll/snapback
# Update [1.1]: there's no need to do the following
# on _both_ height and width - vscroll is never lost,
# and just width catches both Android device rotations
# and PC width-only resizes; but it is required on width
# for both android and PCs, else hscroll is lost on all
# else hscroll lost on rotation (kivy bug)
# on_height: root.enable_logs_text_hscroll() # extraneous [1.1]
# else hscroll lost on width-only PC resizes
on_width: root.enable_logs_text_hscroll() # necessary+sufficient
TextInputNoSelect:
id: logfiletext
text: 'Logfile content...'
multiline: True
do_wrap: False
background_color: logsbackgroundcolor.text
foreground_color: logsforegroundcolor.text
# and selection+handles+bubbles disabled in .py
# [1.5] see also kivy's use_handles, use_bubbles
readonly: True # else keyboard still covers (iff font?)
# default Roboto-Regular
# also DejaVuSans, Roboto-{Bold, Italic, BoldItalic}
font_name: 'RobotoMono-Regular'
# to enable scrolling
size_hint: (None, None)
width: logfilescroll.width
height: max(self.minimum_height, logfilescroll.height)
# on each text change? NO - not required here
# run this only on logfile load + scroll height
# on_text: root.enable_logs_text_hscroll()
# unlike others, this is hscrolled and not wrapped
# CAUTION: the .py's hscroll size calc includes this padding
# padding: [24, 24] # [h, v], mind the screen curves
padding: [dp(12), dp(12)] # scale to screen density [1.1]
#------------------------------------------------------------------------------
# CONFIG TAB
#------------------------------------------------------------------------------
TabbedPanelItem:
id: configtab
text: 'Config'
# [1.5] Wrap the whole setings area in a ScrollView for very
# small displays? NO: could not get this to scroll as coded,
# and it fits most contexts as is, except for narrow-phones
# landscape, which is likely unusable in general. The Main
# tab is also unscrolled, because it fits every practical size.
#
# UPDATE: changes to properly vertically align the text inputs
# in Config also made this tab much more usable in landscape
# mode on slab phones - which was a primary driver for scrolls.
# This similarly improves the Main tab's pathnames on slabs.
BoxLayout:
orientation: 'vertical'
# Start setting-widgets area
# ScrollView: # [1.5] PUNT
# BoxLayout:
# Checkbutton + Label
GridLayout:
size_hint_y: 0.40
cols: 2
# padding: [16, 16, 0, 0] # [l, t, r, b]
padding: [dp(8), dp(8), 0, 0] # scale to screen density [1.1]
# [1.4] reordered to group now-three chooser settings
ConfigCheckbox:
size_hint_x: 0.20
id: backupscheckbox
active: True
on_press: root.on_backups_clicked(self.active) # not on_touch
LineLabel:
size_hint_x: 0.80
text: 'Back up SYNC changes in TO for UNDO?' # [1.4] shorter
ConfigCheckbox:
size_hint_x: 0.20
id: skipcruftscheckbox
active: True
LineLabel:
size_hint_x: 0.80
text: 'Skip one-platform items in FROM and TO?' # [1.4] shorter
ConfigCheckbox:
size_hint_x: 0.20
id: runactionasservice # force off in .py for Android 8
active: True
LineLabel:
size_hint_x: 0.80
id: runactionasservicelabel
text: 'Run Main actions as services on Android?'
# [1.1] allow users to toggle keep-screen-on;
# for both Android (timeout) and PCs (sceensaver);
# also reordered toggles and tweaked sizes for fit;
ConfigCheckbox:
size_hint_x: 0.20
id: keepscreenon
active: True
on_press: root.on_keepscreenon_clicked(self.active)
LineLabel:
size_hint_x: 0.80
text: 'Keep the screen on while using this app?'
# [1.4] Togggle hiddens view via filechooser.show_hidden.
# The newly opened popup's filechooser show_hidden is set in
# .py from this Main Config-tab setting, which is persisted.
# filechooser's is_hidden is already guarded against hangs.
ConfigCheckbox:
size_hint_x: 0.20
id: showhiddens
active: False
LineLabel:
size_hint_x: 0.80
text: 'Show hidden folders in choosers?'
ConfigCheckbox:
size_hint_x: 0.20
id: rootfolderselections
active: False
LineLabel:
size_hint_x: 0.80
text: 'Show root folder in choosers if possible?'
ConfigCheckbox:
size_hint_x: 0.20
id: appfolderselections
active: True
LineLabel:
size_hint_x: 0.80
id: appfolderselectionslabel
text: 'Show app folder in choosers on Android?'
# Input + Label
GridLayout:
size_hint_y: 0.25
cols: 2
# padding: [16, 16, 0, 0] # [l, t, r, b]
padding: [dp(8), dp(8), 0, 0] # scale to screen density [1.1]
TextInput:
size_hint_x: 0.20
id: maxnumbackups
text: '25'
# [1.5] filter limits to 0-9 but allows '' (crashes): default in .py
input_filter: 'int'
on_focus: if not self.focus: root.on_int_input_defocus('maxnumbackups')
multiline: False
#valign: 'middle' # moot, nonexistent in Text
# [1.5] valign properly, per current size
padding_y: (self.height - self.line_height) / 2
padding_x: dp(8)
#padding: [16, 16, 16, 0] # [l, t, r, b], default [6]*4 (pixels)
#padding: [dp(8), 0, dp(8), 0] # scaled per density [1.1]
#height: self.minimum_height # lines height, includes padding
#size_hint_y: 1
LineLabel:
size_hint_x: 0.80
text: 'Keep up to this many SYNC backups'
TextInput:
size_hint_x: 0.20
id: maxnumlogfiles
text: '30'
# [1.5] filter limits to 0-9 but allows '' (crashes): default in .py
input_filter: 'int'
on_focus: if not self.focus: root.on_int_input_defocus('maxnumlogfiles')
multiline: False
#valign: 'middle' # moot, nonexistent in Text
# [1.5] valign properly, per current size
padding_y: (self.height - self.line_height) / 2
padding_x: dp(8)
#padding: [16, 16, 16, 0]
#padding: [dp(8), 0, dp(8), 0] # scaled per density [1.1]
LineLabel:
size_hint_x: 0.80
text: 'Keep up to this many run logfiles'
TextInput:
size_hint_x: 0.20
id: maxlogstailsize
text: str(10 * 1024)
# [1.5] filter limits to 0-9+',' but allows '' (crashes): default in .py
input_filter: lambda s, u: s if s in '0123456789,' else ''
on_focus: if not self.focus: root.on_int_input_defocus('maxlogstailsize')
multiline: False
#valign: 'middle' # moot, nonexistent in Text
# [1.5] valign properly, per current size
padding_y: (self.height - self.line_height) / 2
padding_x: dp(8)
#padding: [16, 16, 16, 0]
#padding: [dp(8), 0, dp(8), 0] # scaled per density [1.1]
LineLabel:
size_hint_x: 0.80
text: 'Show up to this many bytes in Logs TAIL'
# Input + Button + Label
GridLayout:
size_hint_y: 0.35
cols: 3
# padding: [16, 16, 0, 0] # [l, t, r, b]
padding: [dp(8), dp(8), 0, 0] # scaled per density [1.1]
TextInputNoSelect:
size_hint_x: 0.20
id: logsbackgroundcolor
text: 'black'
readonly: True
disabled: True
multiline: False
#valign: 'middle' # moot, nonexistent in Text
# [1.5] valign properly, per current size
padding_y: (self.height - self.line_height) / 2
padding_x: dp(8)
#padding: [16, 16, 16, 0]
#padding: [dp(8), dp(8), dp(8), 0] # scaled per density [1.1]
ActivityButton:
size_hint_x: 0.20
text: 'Pick'
on_release: root.pickcolor(root.do_bg_color, 'Background')
LineLabel:
size_hint_x: 0.60
text: 'Logs-display background color'
TextInputNoSelect:
size_hint_x: 0.20
id: logsforegroundcolor
text: 'white'
readonly: True
disabled: True
multiline: False
#valign: 'middle' # moot, nonexistent in Text
# [1.5] valign properly, per current size
padding_y: (self.height - self.line_height) / 2
padding_x: dp(8)
#padding: [16, 16, 16, 0]
#padding: [dp(8), dp(8), dp(8), 0] # scaled per density [1.1]
ActivityButton:
size_hint_x: 0.20
text: 'Pick'
on_release: root.pickcolor(root.do_fg_color, 'Foreground')
LineLabel:
size_hint_x: 0.60
text: 'Logs-display foreground color'
TextInput:
size_hint_x: 0.20
id: globalfontsize
text: '40'
# [1.5] filter limits to 0-9 but allows '' (crashes): default in .py
input_filter: 'int'
on_focus: if not self.focus: root.on_int_input_defocus('globalfontsize')
multiline: False
#valign: 'middle' # moot, nonexistent in Text
# [1.5] valign properly, per current size
padding_y: (self.height - self.line_height) / 2
padding_x: dp(8)
#padding: [16, 16, 16, 0]
#padding: [dp(8), dp(8), dp(8), 0] # scaled per density [1.1]
ActivityButton:
size_hint_x: 0.20
text: 'Apply'
on_release: root.set_font_size(globalfontsize.text) # [1.5] .py traps ''
LineLabel:
size_hint_x: 0.60
text: 'Global font size in pixels'
# Radio button + Radio button + Label
# chooser popup uses these on open, and sets them on changes;
# setting in config doesn't impact chooser until popup open;
# one might expect that pressing a radio-group button selects it
# and clears the other, but one would be wrong in kivy - without
# the on_press, it's possible to have none selected in a group.
# UPDATE: the scantly documented allow_no_selection does the
# trick, but weirdly must be declared for each item in the group.
# also now used in Logs' file chooser too, but in .py, not .kv.
ToggleButton:
size_hint_x: 0.20
id: mainchoosericon
text: 'Icon'
group: 'mainchooserstyle'
state: 'down'
# on_press: self.state = 'down'; mainchooserlist.state = 'normal'
allow_no_selection: False
ToggleButton:
size_hint_x: 0.20
id: mainchooserlist
text: 'List'
group: 'mainchooserstyle'
# on_press: self.state = 'down'; mainchoosericon.state = 'normal'
allow_no_selection: False
LineLabel:
size_hint_x: 0.60
text: 'Style of Main folder chooser'
# End setting-widgets area
# Tab remembers, but saves for non-run items here only on
# manual Save tap, not auto on Main run. Save here saves
# just this tab's settings, Main run saves Main paths only.
#BoxLayout:
# size_hint_y: 0.10
# # padding: [0, 16, 0, 0] # [l, t, r, b]
# padding: [0, dp(8), 0, 0] # scaled per density [1.1]
# height: buttonheight
# orientation: 'horizontal'
Label:
size_hint: None, None
height: dp(8) # custom top space above buttons
DialogButtonsRows1: # [1.5] uniform fixed size
DialogButton:
text: 'Save Changes'
on_release: root.save_persisted_settings_Config()
DialogButton:
text: 'Restore Defaults'
on_release: root.reset_persisted_settings_Config()
#------------------------------------------------------------------------------
# HELP TAB
#------------------------------------------------------------------------------
TabbedPanelItem:
id: helptab
text: 'Help'
BoxLayout:
orientation: 'vertical'
#SectionLabel:
# size_hint_y: 0.10
# text: 'Usage Essentials'
ScrollView:
# because TextInput itself would not scroll horizontally
size_hint_y: 0.95 # [1.5] was 0.70
size_hint_x: 1.0
id: helptextscroll
do_scroll_x: False
do_scroll_y: True
effect_cls: 'ScrollEffect' # don't overscroll/snapback
# [1.5] use multiple Labels for text markup
# Text fields don't do markup, but Labels display as empty
# space for any trivially-large text. To work around, split
# text into N parts and span across multiple Labels in .py.
# Labels allow markup: bold, italics, colors, [H] headers.
# CATCH: unlike Kivy 2.31, 2.1.0 Label padding is always just
# two values, [h v], so must pad manually at top and bottom of
# the split labels box. See <LabelTextDisplay> for more info.
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
VerticalSpacer10: # add padding at top: kivy 2.1 padding is 2 vals
SplitTextBoxLayout: # main.py adds N LabelTextDisplay for split text
id: helptextbox # this precludes a common Help/About class
VerticalSpacer10: # add padding at bottom: kivy 2.1 padding 2 vals
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# [1.5] prior version - retain temp
# but this text is wrapped, and does not hscroll
# else hscroll lost on rotation (kivy bug)
# on_height: root.enable_logs_text_hscroll()
#
#TextInputNoSelect:
# id: helptext
# text: root.help_message
# multiline: True
# readonly: True
# allow_copy: False # else odd select-all popup
# do_wrap: True
# foreground_color: 'white'
# background_color: '#0f2f2f' # 'black' is hard to read
# font_name: 'RobotoMono-Regular' # default is hard to read
# # padding: [42, 16] # [h, v], mind the screen curves
# padding: [dp(18), dp(8)] # scale to screen density [1.1]
#
# # to enable scrolling
# size_hint: (None, None)
# width: helptextscroll.width
# height: max(self.minimum_height, helptextscroll.height)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# [1.5] Failed attempt: replacing the ScrollView above with the
# following RecycleView cut memory use substantially when Help
# and About are viewed (from ~850M to ~500M), but scrolling
# was too jerky to be usable. Improving this may require fixed-
# size items, and that seems impossible with wrapped-text Labels.
#
#RecycleView:
# id: helptextrview # .py sets .data with line strs
# viewclass: 'LabelTextDisplay'
#
# RecycleBoxLayout:
# padding: [dp(10), dp(10)] # pad top/bottom here, not RV or Lable
# do_scroll_x: False
# do_scroll_y: True
# effect_cls: 'ScrollEffect' # don't overscroll/snapback
#
# default_size: None, None # val2 stops jumpy scrolls, may botch lines
# default_size_hint: 1, None
# size_hint_y: None
# height: self.minimum_height
# orientation: 'vertical'
#
# The .py code simply did this, though updates for fontsize
# changes were never tested or addressed:
#
# # using N > 1 lines per Label did not help here: still jerky
# labelbox = self.ids.helptextrview
# lines = text.splitlines() # 1 line/Label
# labelbox.data = [{'text': line or ' '} for line in lines] # Label.text
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#GridLayout:
# cols: 2
# size_hint_y: 0.20
#BoxLayout:
# size_hint_y: 0.08
# height: buttonheight
DialogButtonsRows1: # [1.5] uniform fixed size, culled, shortened
DialogButton:
text: 'Docs'
on_release: root.open_web_page('https://quixotely.com/PC-Phone USB Sync/User-Guide.html')
DialogButton:
text: 'Website'
on_release: root.open_web_page('https://quixotely.com/PC-Phone USB Sync/index.html')
DialogButton:
text: 'Backups'
on_release: root.do_explore_backups()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# [1.5] retain temp
#DialogButton:
# text: 'App Downloads'
# on_release: root.open_web_page('https://quixotely.com/PC-Phone USB Sync/App-Packages.html')
#
#DialogButton:
# text: 'Mergeall Website'
# on_release: root.open_web_page('https://learning-python.com/mergeall.html')
#
#DialogButton:
# text: 'Mergeall Docs'
# on_release: root.open_web_page('https://learning-python.com/mergeall-products/unzipped/UserGuide.html')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#------------------------------------------------------------------------------
# ABOUT TAB
#------------------------------------------------------------------------------
TabbedPanelItem:
id: abouttab
text: 'About'
BoxLayout:
orientation: 'vertical'
ScrollView:
# now required for spit-text labels box
size_hint_y: 0.95
size_hint_x: 1.0
id: abouttextscroll
do_scroll_x: False
do_scroll_y: True
effect_cls: 'ScrollEffect' # don't overscroll/snapback
# [1.5] use multiple Labels for text markup, and split
# text to avoid blackouts. See notes at Help above.
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
VerticalSpacer10: # add padding at top: kivy 2.1 padding is 2 vals
SplitTextBoxLayout: # main.py adds N LabelTextDisplay for split text
id: abouttextbox # this precludes a common class
VerticalSpacer10: # add padding at bottom: kivy 2.1 padding 2 vals
# [1.5] former scheme - see Help
#TextInputNoSelect:
# foreground_color: 'white'
# background_color: 'black'
DialogButtonsRows1: # [1.5] uniform fixed size, Mergeall from Help
DialogButton:
text: 'Play Store'
on_release: root.open_web_page('https://play.google.com/store/apps/details?id=com.quixotely.usbsync')
DialogButton
text: 'Downloads'
on_release: root.open_web_page('https://quixotely.com/PC-Phone USB Sync/App-Packages.html')
DialogButton:
text: 'Mergeall'
on_release: root.open_web_page('https://learning-python.com/mergeall.html')
#------------------------------------------------------------------------------
# MAIN-TAB FOLDER-CHOOSER POPUP
#------------------------------------------------------------------------------
<MainPathPickDialog>:
# File chooser modal popup, for Main tab's FROM/TO paths
# Pass oncanel, onpick, etc, and fill in storage buttons at top
# [1.5] Hindsight: this coding nests a Boxlayout in a FloatLayout.
# The latter is the superclass of this <> class in the .py and is
# largely pointless: we could just use BoxLayout in the .py and
# skip an indentation level here. OTOH, this allows configuring
# the BoxLayout here instead of in the .py; and if it ain't broke...
BoxLayout:
size: root.size
pos: root.pos
orientation: 'vertical'
ScrollView:
# scroll horizontally for large fonts or names, and many devices
# nit: may be moot on phones (>1 removable?), pc windows can expand
# but usb hubs _do_ work on android, and large fonts are possible
# nit: fixed-size child seems to preclude expanding into hspace,
# but kivy doesn't have css's minwidth equiv (scroll at <= this);
# that is, kivy seems to dictate scroll or expand, but not both;
# in principle, might bind scrollbar size to a callback that calcs
# normal texture-based size of buttons and hence boxlayout, and
# distributes excess evenly among buttons if scrollbar is wider;
# but new layout seems better: space at end implies more options;
size_hint: (1, None)
id: devicebuttonsscroll
do_scroll_x: True # auto opens at top of text
effect_cls: 'ScrollEffect' # don't overscroll/snapback
height: tallbuttonheight # a wee bit taller for taps
width: root.width
BoxLayout:
# the .py adds variable number ToggleButton childen here;
# note: dynamic is a whole lot harder with scrolling;
# also catches tap of already down toggle button: goto root
# note: catching down taps adds a full order of complexity,
# because normal button events don't fire: see on_touch_down;
id: devicebuttons
orientation: 'horizontal'
# all sizing/scrolling now done in .py do_main_path()
# size_hint: (None, None)
# height: devicebuttonsscroll.height
# width: max(self.minimum_width, devicebuttonsscroll.width)
# width: self.minimum_width <= avoid triggering scroll too soon
# UPDATE: boxlayout sizing is now here - simpler than in .py,
# and works sans buttons (though buttons must be sized in .py
# when built); in kivy, the docs are so thin that you have to
# do it the wrong way first - and a few times; please improve!
size_hint: (None, 1)
width: self.minimum_width # set to min-width when computed
BoxLayout:
size_hint_y: None
height: tallbuttonheight # rarely used, but match others
orientation: 'horizontal'
# these are not radio buttons (group) because the down shading
# would be too distracting here; their counterparts in the
# Configs tab are radios (and set by the on_releases below)
Button:
text: 'Icon View'
on_release: filechooser.view_mode = 'icon'; root.onviewmode('icon')
Button:
text: 'List View'
on_release: filechooser.view_mode = 'list'; root.onviewmode('list')
# [1.4] Punt: logic differs from icon/list here, and rarer - make a config only
#Button:
# text: 'Hidden'
# on_release: filechooser.show_hidden = not filechooser.show_hidden # nope
PathDisplayNoSelect:
id: pathname
text: root.pickstart
# and selection+handles+bubbles disabled in .py
# PUNT: True disables scrolls, ScrollView hurts, and inputs goto Main
# readonly: True # because changes have no effect on chooser
size_hint: (1, None)
height: self.minimum_height
# width: max(self._get_row_width(0), pathnamescroll.width)
# width: max(len(pathname.text), pathnamescroll.width)
FileChooser:
id: filechooser
path: root.pickstart # reset on device change (creation arg property)
show_hidden: False # [1.4] .py now sets on popup open from Config tab
# per source code, mode names 'list' and 'icon' don't exist until
# after choosers below are built, but no code allowed after here (!$#);
# set this only in the .py after popup built, and by buttons above
# view_mode: root.viewmode # set in .py by id, based on Config tab radio btns
# limit navigation to the start|reset folder and below
# removes '..' to avoid uncaught excs for folders sans access
rootpath: root.rootpath # reset on device change (creation arg property)
# filter out files: show+select dirs only
# filters filters _in_, filter_dirs: False adds all dirs _in_
# or filters: [lambda p, f: os.path.isdir(os.path.join(p, f))]
# sort_func(x, y): default=folders first, uppercase first (macOS)
dirselect: True # allow dir slections
filter_dirs: False # add back all dirs post filters
filters: [(lambda path, file: False)] # remove all files+dirs at first
# update text field on taps, fetch from there
on_selection: pathname.text = self.selection and self.selection[0] or ''
# view_mode: 'list'
# choose one by magic buttons above, icons first per order;
# font size in the first is fixed by a template copy/mod ahead here;
# label "Size" in the second is nuked by children-lists crawl in .py;
FileChooserIconLayout
id: filechoosericonlayout
FileChooserListLayout
id: filechooserlistlayout # for 'Size' nuke
#BoxLayout:
# size_hint_y: None
# height: tallbuttonheight # no need for tall at bottom, but match
DialogButtonsRows1: # [1.5] uniform fixed size
DialogButton:
text: 'Cancel'
on_release: root.oncancel(root) # validate root=content instance
DialogButton:
text: 'Pick'
on_release: root.onpick(pathname.text, root)
#------------------------------------------------------------------------------
# INFO POPUP
#------------------------------------------------------------------------------
<InfoDialog>:
# General modal info popup: pass message, oncancel
# Toast alternative (must also declare class in .py)
# [1.5] BoxLayout in FloatLayout - see MainPathPickDialog
BoxLayout:
size: root.size
pos: root.pos
orientation: 'vertical'
# ([1.5] now moot, but retained temp for reference/context)
# 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:
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
SpacedLabelTextDisplay: # [1.5] Label for colors and headers
text: root.message # with padding on top and bottom
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#retain temp
#TextInputNoSelect:
# text: root.message
# multiline: True
# readonly: True # True: no keyboard on tap [1.1]
# foreground_color: 'white'
# background_color: 'black'
# # padding: [24, 24] # [h, v], mind the screen curves
# padding: [dp(10), dp(10)] # scale to screen density [1.1]
#
# # to enable scrolling
# size_hint: (None, None)
# width: textscroll.width
# height: max(self.minimum_height, textscroll.height)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#BoxLayout:
# size_hint_y: None
# height: buttonheight
DialogButtonsRows1: # [1.5] uniform fixed size
DialogButton:
text: 'Okay'
on_release: root.oncancel(root) # validate root=content instance
#------------------------------------------------------------------------------
# CONFIRM POPUP
#------------------------------------------------------------------------------
<ConfirmDialog>:
# General modal verify popup: pass message, onyes, onno
# [1.5] BoxLayout in FloatLayout - see MainPathPickDialog
BoxLayout:
size: root.size
pos: root.pos
orientation: 'vertical'
# see scrolling docs at InfoDialog above
ScrollView:
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
SpacedLabelTextDisplay: # [1.5] Label for colors and headers
text: root.message # with padding on top and bottom
#TextInputNoSelect: # former scheme - see InfoDialog
#BoxLayout:
# size_hint_y: None
# height: buttonheight
DialogButtonsRows1: # [1.5] uniform fixed size
DialogButton:
text: 'No'
on_release: root.onno(root) # validate root=content instance
DialogButton:
text: 'Yes'
on_release: root.onyes(root)
#------------------------------------------------------------------------------
# COLOR POPUP
#------------------------------------------------------------------------------
<ColorPickDialog>:
# Prebuilt content: pass oncancel, onpick
# [1.5] BoxLayout in FloatLayout - see MainPathPickDialog
BoxLayout:
size: root.size
pos: root.pos
orientation: 'vertical'
ColorPicker
id: colorpicker
#BoxLayout:
# orientation: 'horizontal'
# size_hint_y: None
# height: buttonheight
DialogButtonsRows1: # [1.5] uniform fixed size
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)
#------------------------------------------------------------------------------
# TRIAL-ENDED POPUP
#------------------------------------------------------------------------------
<TrialEndedDialog>:
# NO LONGER USED, as of [1.3]
# After N opens in trial app: pass message, oncancel
# This might be subverted, but it's not worth the struggles
# [1.5] BoxLayout in FloatLayout - see MainPathPickDialog
BoxLayout:
size: root.size
pos: root.pos
orientation: 'vertical'
# see scrolling docs at InfoDialog above
ScrollView:
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
SpacedLabelTextDisplay: # [1.5] Label for colors and headers
text: root.message # with padding on top and bottom
#TextInputNoSelect: # former scheme - see InfoDialog
#BoxLayout:
# size_hint_y: None
# height: buttonheight
DialogButtonsRows1: # [1.5] uniform fixed size
DialogButton:
# mind the app.root.method
text: 'Get Full App'
on_release: app.root.open_web_page('https://play.google.com/store/apps/details?id=com.quixotely.usbsync')
DialogButton:
text: 'Exit Trial App'
on_release: root.oncancel() # no root=content validate: stopping (moot)
#------------------------------------------------------------------------------
# NAME-MODE POPUP
#------------------------------------------------------------------------------
<ConfirmNameDialog>:
# Confirm NAME action by a report|update mode choice
# Pass message, onrunreport, onrunupdate, oncancel
# [1.5] BoxLayout in FloatLayout - see MainPathPickDialog
BoxLayout:
size: root.size
pos: root.pos
orientation: 'vertical'
# see scrolling docs at InfoDialog above
ScrollView:
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
SpacedLabelTextDisplay: # [1.5] Label for colors and headers
text: root.message # with padding on top and bottom
#TextInputNoSelect: # former scheme - see InfoDialog
#BoxLayout:
# size_hint_y: None
# height: buttonheight
DialogButtonsRows1: # [1.5] uniform fixed size
DialogButton:
text: 'Cancel'
on_release: root.oncancel(root) # validate root=content instance
DialogButton:
text: 'Run Report'
on_release: root.onrunreport(root)
DialogButton:
text: 'Run Update'
on_release: root.onrunupdate(root)
#------------------------------------------------------------------------------
# KIVY FILECHOOSER BUG WORKAROUND (for which this app is deeply ashamed...)
#------------------------------------------------------------------------------
# (NOTE: this might not be used in later Kivy, but is used in 2.1.0)
#
# The filechooser's icon view clips text vertically when fonts are
# set larger than usual by the app or user settings. This may be rare
# on phones, but is common elsewhere (e.g., Linux + high-res screens).
#
# There seems no way to fix this apart from the following. Kivy loads
# its own "style.kv" file first from its intall's data/ folder, before
# loading user .kv files (like this) or running Builder calls in the .py.
# The style.kv file defines styles for all Kivy builtin widgets, including
# the filechooser, which uses both list+icon views and a generic controller.
#
# To fix, copy the FileIconEntry template code in style.kv to here, and
# mod the filename label to use size settings that make it as tall as needed
# for the text's 'texture' (font/scaling) size. See the '##[ML' mods below.
# This avoids changing the built-in style.kv, but depends on its contents.
# Note that Label has texture, but TextInput has minumum_height (confusingy).
#
# This relies perilously on Kivy implementation details and is subpar,
# but there is no other known option, and filename clips are a quite bad
# user experience (along with the ~dozen other Kivy bugs worked around).
# Why this sizing scheme isn't used in the filechooser is anybody's guess.
#
# In Kivy's defense, there usually ARE ways to work around such things
# because it's all Python down to the graphics layer. But its user base
# might expand with a few crucial docs and fixes. See also orange dots
# left by double clicks on PCs; black boxes in TextInput for long lines;
# lag for loading text into TextInputs; hangs on app close on Android;
# builder battles; the .py's most horrible hack ever to kill the bogus
# "Size" label in FileChooserListLayout which is useless for folders and
# showed up just as an "e" for some phones and/or font sizes; plus all
# the other bugs zapped here and in the .py (frustration happens).
#
# Hindsight: this may have been do-able with children-list crawls like
# the list-layout "Size" fix in the .py, instead of this code copy+mod.
# OTOH, this works as is, and children lists seem just as brittle.
# This, and more, should really be exposed for mods via ids or API.
#
# References:
# https://github.com/kivy/kivy/blob/master/kivy/uix/filechooser.py
# https://github.com/kivy/kivy/blob/master/kivy/data/style.kv
# https://kivy.org/doc/stable/api-kivy.uix.label.html#sizing-and-text-content
# REDEF BUILT-IN WIDGET'S STYLE
[FileIconEntry@Widget]:
locked: False
path: ctx.path
selected: self.path in ctx.controller().selection
size_hint: None, None
on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])
on_touch_up: self.collide_point(*args[1].pos) and ctx.controller().entry_released(self, args[1])
size: '100dp', '100dp'
canvas:
Color:
rgba: 1, 1, 1, 1 if self.selected else 0
##[ML test] rgba: 99, 99, 99, 99 if self.selected else 0
BorderImage:
border: 8, 8, 8, 8
pos: root.pos
size: root.size
source: 'atlas://data/images/defaulttheme/filechooser_selected'
Image:
size: '48dp', '48dp'
source: 'atlas://data/images/defaulttheme/filechooser_%s' % ('folder' if ctx.isdir else 'file')
pos: root.x + dp(24), root.y + dp(40)
Label:
text: ctx.name
font_name: ctx.controller().font_name
##[ML mod] text_size: (root.width, self.height)
halign: 'center'
shorten: True
##[ML mod] size: '100dp', '16dp'
pos: root.x, root.y + dp(16)
##[ML add]
text_size: root.width, None
size: self.texture_size
Label:
text: '{}'.format(ctx.get_nice_size())
font_name: ctx.controller().font_name
font_size: '11sp'
color: .8, .8, .8, 1
size: '100dp', '16sp'
pos: root.pos
halign: 'center'
#******************************************************************************
# ONLY DEFUNCT CODE FOLLOWS
#******************************************************************************
#------------------------------------------------------------------------------
# NO LONGER USED: now embedded in Logs tab...
#------------------------------------------------------------------------------
#<LogfilePickDialog>:
#
# # File chooser modal popup, for Logs tab's logfile path
# # Use popup so always has surrent folder contents.
# # Only show logfiles: no folder navigation required.
#
# BoxLayout:
# size: root.size
# pos: root.pos
# orientation: 'vertical'
#
# PathDisplay:
# id: pathname
# text: root.pickstart
# readonly: True
#
# FileChooser:
# id: filechooser
# path: root.pickstart
# show_hidden: False
# rootpath: root.pickstart # drop '..' in picker
#
# # show+select logfiles only, no dirs, listview only
# dirselect: False
# filter_dirs: True
# filters: ['*date*-time*--*.txt']
#
# on_selection: pathname.text = self.selection and self.selection[0] or ''
# FileChooserListLayout
#
# BoxLayout:
# size_hint_y: None
# height: buttonheight
#
# DialogButton:
# text: 'Cancel'
# on_release: root.oncancel(root)
#
# DialogButton:
# text: 'Pick'
# on_release: root.onpick(pathname.text, root)
#------------------------------------------------------------------------------
# NO LONGER USED: old configs and config-tab cruft, tmi...
#------------------------------------------------------------------------------
#SectionLabel:
# size_hint_y: 0.10
# text: 'Run Options'
#
#SectionLabel:
# size_hint_y: 0.10
# text: 'Appearance'
#
#Button:
# text: 'Tip'
# on_release:
# root.info_message('SYNC backups are in TO/__bkp__',
# usetoast=True, longtime=True)
#Just always log, but make the folder self-cleanding to avoid space bloat
#BoxLayout:
# orientation: 'horizontal'
# CheckBox:
# id: logfilescheckbox
# active: True
# Label:
# text: 'Save Logfiles for Main-Tab Actions?'
#Show on Logs tab, as uneditable/unselectable text; else tmi...
#BoxLayout:
# orientation: 'horizontal'
# Label:
# text: 'Logfiles folder:'
# TextInput:
# id: logfilesfolder
#Just test os.listdir and auto display ROOT if have access to "/"
#BoxLayout:
# id: rootfolderbox
# orientation: 'horizontal'
# CheckBox:
# id: rootfolderselections
# active: False
# Label:
# text: 'Allow Root Folder Selections'
#Way too much: skip list + keep list, case, pattern syntax, module mods
#BoxLayout:
# orientation: 'horizontal'
# Label:
# text: 'Cruft Names Pattern:'
# TextInput:
# id: cruftnamespattern
#Now a Main action: prestep too odd to wedge into thread-launch model
#Label:
# text: ''
#
#BoxLayout:
# id: fixfilenamesbox
# orientation: 'horizontal'
# CheckBox:
# id: fixfilenamescheckbox
# active: True
# Label:
# text: 'Fix Nonportable Filenames in FROM?'