Skip to content

Commit

Permalink
IP: Can mark group as Do Not Download
Browse files Browse the repository at this point in the history
Remaining work:
* Needs tests
* Needs release notes
* Needs DOCUMENTATION in wiki
* Needs commit message that references #72
* Hide "Sequential" option in UI until implementation complete in future commit
  • Loading branch information
davidfstr committed Jan 20, 2024
1 parent ce7dc22 commit 50a60ad
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 28 deletions.
23 changes: 18 additions & 5 deletions src/crystal/browser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

class MainWindow:
project: Project
frame: wx.Frame
_frame: wx.Frame
entity_tree: EntityTree
task_tree: TaskTree

Expand Down Expand Up @@ -492,17 +492,29 @@ def _on_new_group(self, event: wx.CommandEvent) -> None:
pass

@fg_affinity
def _on_new_group_dialog_ok(self, name: str, url_pattern: str, source: ResourceGroupSource) -> None:
def _on_new_group_dialog_ok(self,
name: str,
url_pattern: str,
source: ResourceGroupSource,
do_not_download: bool,
) -> None:
# TODO: Validate user input:
# * Is url_pattern empty?
# * Is url_pattern already taken?
ResourceGroup(self.project, name, url_pattern, source)
rg = ResourceGroup(self.project, name, url_pattern, source)
rg.do_not_download = do_not_download

@fg_affinity
def _on_edit_group_dialog_ok(self, rg: ResourceGroup, name: str, url_pattern: str, source: ResourceGroupSource) -> None:
def _on_edit_group_dialog_ok(self,
rg: ResourceGroup,
name: str,
url_pattern: str,
source: ResourceGroupSource,
do_not_download: bool,
) -> None:
if url_pattern != rg.url_pattern:
raise ValueError()
(rg.name, rg.source) = (name, source)
(rg.name, rg.source, rg.do_not_download) = (name, source, do_not_download)

# TODO: This update should happen in response to an event
# fired by the entity itself.
Expand Down Expand Up @@ -548,6 +560,7 @@ def _on_edit_entity(self, event) -> None:
initial_url_pattern=rg.url_pattern,
initial_source=rg.source,
initial_name=rg.name,
initial_do_not_download=rg.do_not_download,
is_edit=True)
except CancelLoadUrls:
pass
Expand Down
215 changes: 200 additions & 15 deletions src/crystal/browser/new_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
CreateButtonSizer, position_dialog_initially, ShowModal,
)
from crystal.util.wx_static_box_sizer import wrap_static_box_sizer_child
from crystal.util.xos import is_linux
from crystal.util.xos import is_linux, is_mac_os, is_windows
import sys
from typing import Callable, Optional, Tuple, Union
import wx
Expand All @@ -18,6 +18,11 @@
_WINDOW_INNER_PADDING = 10
_FORM_LABEL_INPUT_SPACING = 5
_FORM_ROW_SPACING = 10
_SCROLL_RATE = 20
_ABOVE_OPTIONS_PADDING = 10

_OPTIONS_SHOWN_LABEL = 'Basic Options'
_OPTIONS_NOT_SHOWN_LABEL = 'Advanced Options'


class NewGroupDialog:
Expand All @@ -28,12 +33,13 @@ class NewGroupDialog:

def __init__(self,
parent: wx.Window,
on_finish: Callable[[str, str, ResourceGroupSource], None],
on_finish: Callable[[str, str, ResourceGroupSource, bool], None],
project: Project,
saving_source_would_create_cycle_func: Callable[[ResourceGroupSource], bool],
initial_url_pattern: str='',
initial_source: ResourceGroupSource=None,
initial_name: str='',
initial_do_not_download: bool=False,
is_edit: bool=False,
) -> None:
"""
Expand Down Expand Up @@ -77,12 +83,21 @@ def __init__(self,
preview_box_flags,
preview_box_border,
) = self._create_preview_box(dialog)
content_sizer.Add(preview_box, proportion=1, flag=wx.EXPAND|preview_box_flags, border=preview_box_border)
content_sizer.Add(
preview_box,
proportion=1,
flag=wx.EXPAND|preview_box_flags,
border=preview_box_border)

self._options_sizer = self._create_advanced_options(dialog, initial_do_not_download)
content_sizer.Add(
self._options_sizer,
flag=wx.EXPAND|wx.TOP,
border=_ABOVE_OPTIONS_PADDING)

ok_button_id = (wx.ID_NEW if not is_edit else wx.ID_SAVE)
dialog_sizer.Add(content_sizer, proportion=1, flag=wx.EXPAND|wx.ALL,
border=_WINDOW_INNER_PADDING)
dialog_sizer.Add(CreateButtonSizer(dialog, ok_button_id, wx.ID_CANCEL), flag=wx.EXPAND|wx.BOTTOM,
dialog_sizer.Add(self._create_buttons(dialog, is_edit), flag=wx.EXPAND|wx.BOTTOM,
border=_WINDOW_INNER_PADDING)

if not fields_hide_hint_when_focused():
Expand All @@ -94,8 +109,9 @@ def __init__(self,
self._update_preview_urls()

position_dialog_initially(dialog)
dialog.Show(True)
dialog.Fit()
self._on_options_toggle() # collapse options initially
dialog.Show(True)

dialog.MinSize = dialog.Size
dialog.MaxSize = wx.Size(wx.DefaultCoord, wx.DefaultCoord)
Expand Down Expand Up @@ -195,7 +211,8 @@ def _create_preview_box(self, parent: wx.Window) -> Tuple[Union[wx.Window, wx.Si
preview_box_border = 10

preview_box_root_sizer.Add(
wrap_static_box_sizer_child(self._create_preview_box_content(preview_box_root)),
wrap_static_box_sizer_child(
self._create_preview_box_content(preview_box_root)),
proportion=1,
flag=wx.EXPAND)

Expand All @@ -217,6 +234,144 @@ def _create_preview_box_content(self, parent: wx.Window) -> wx.Sizer:

return content_sizer

def _create_advanced_options(self, parent: wx.Window, *args, **kwargs) -> wx.StaticBoxSizer:
options_sizer = wx.StaticBoxSizer(wx.VERTICAL, parent, label='Advanced Options')
options_sizer.Add(
wrap_static_box_sizer_child(
self._create_advanced_options_content(
options_sizer.GetStaticBox(),
*args, **kwargs)),
flag=wx.EXPAND)
return options_sizer

def _create_advanced_options_content(self,
parent: wx.Window,
initial_do_not_download: bool,
) -> wx.Sizer:
options_sizer = wx.BoxSizer(wx.VERTICAL)

options_sizer.Add(
wx.StaticText(parent, label='These options reset when Crystal quits.'),
)

self.do_not_download_checkbox = wx.CheckBox(
parent, label='Do not download members when embedded',
name='cr-new-group-dialog__do-not-download-checkbox')
self.do_not_download_checkbox.Value = initial_do_not_download
options_sizer.Add(
self.do_not_download_checkbox,
flag=wx.TOP,
border=8,
)
options_sizer.AddSpacer(_FORM_ROW_SPACING)

sequential_row = wx.BoxSizer(wx.HORIZONTAL)
if True:
self.sequential_checkbox = wx.CheckBox(
parent, label='Sequential members',
name='cr-new-group-dialog__sequential-checkbox')
minimum_choice_values = ['0', '1', '2']
# TODO: Allow the user to specify any integer value, via a wx.ComboBox
self.minimum_choice_box = wx.Choice(
parent,
choices=minimum_choice_values)
self.minimum_choice_box.Selection = minimum_choice_values.index('2')

sequential_row.Add(self.sequential_checkbox, flag=wx.CENTER)
sequential_row.Add(wx.StaticText(parent, label='–'), flag=wx.CENTER)
sequential_row.AddSpacer(_FORM_LABEL_INPUT_SPACING)
sequential_row.Add(wx.StaticText(parent, label='Minimum:'), flag=wx.CENTER)
sequential_row.AddSpacer(_FORM_LABEL_INPUT_SPACING)
sequential_row.Add(self.minimum_choice_box, flag=wx.CENTER)

options_sizer.Add(sequential_row)

wildcard_row_scroller = wx.ScrolledWindow(parent, style=wx.HSCROLL|wx.ALWAYS_SHOW_SB)
if True:
wildcard_row = wx.BoxSizer(wx.HORIZONTAL)
if True:
url_pattern_fragments = [
'https://xkcd.com/',
'/',
#'/longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongmoremoremore',
]
url_pattern_wildcards = ['#']
assert len(url_pattern_fragments) == len(url_pattern_wildcards) + 1

wildcard_labels = [
wx.StaticText(wildcard_row_scroller, label=upf)
for upf in url_pattern_fragments
]
for wl in wildcard_labels:
wl.SetForegroundColour(
wx.Colour(
255 * 5//8,
255 * 5//8,
255 * 5//8,
) # light gray
)

wildcard_buttons = [
wx.RadioButton(
wildcard_row_scroller,
label=upw,
style=(wx.RB_GROUP if i == 0 else 0))
for (i, upw) in enumerate(url_pattern_wildcards)
]
wildcard_buttons[0].Value = True # select first wildcard by default

for (wl, wb) in zip(wildcard_labels, wildcard_buttons):
wildcard_row.Add(wl, flag=wx.CENTER)
wildcard_row.AddSpacer(_FORM_LABEL_INPUT_SPACING)
wildcard_row.Add(wb, flag=wx.CENTER)
wildcard_row.AddSpacer(_FORM_LABEL_INPUT_SPACING)
wildcard_row.Add(wildcard_labels[-1], flag=wx.CENTER)

# NOTE: MUST SetScrollRate to something for scrollbars to work
wildcard_row_scroller.SetScrollRate(_SCROLL_RATE, 0)

wildcard_row_scroller.SetSizer(wildcard_row)
virtual_size = wildcard_row.ComputeFittingWindowSize(wildcard_row_scroller)
wildcard_row_scroller.SetMinSize(wx.Size(
# Prevent scroller's min width from inheriting its virtual min width,
# so that the parent can define the width of the scroller
0,
# Force scroller's min height to match its virtual min height
virtual_size.Height
))

if is_mac_os():
checkbox_text_x = 20
elif is_windows():
checkbox_text_x = 16
elif is_linux():
checkbox_text_x = 24
else:
raise AssertionError()
options_sizer.Add(
wildcard_row_scroller,
flag=wx.LEFT|wx.EXPAND,
# Indent the wildcard row such that its left edge aligns with
# the "Sequential members" checkbox label
border=checkbox_text_x)

return options_sizer

def _create_buttons(self, parent: wx.Window, is_edit: bool) -> wx.Sizer:
ok_button_id = (wx.ID_NEW if not is_edit else wx.ID_SAVE)

button_sizer = wx.BoxSizer(wx.HORIZONTAL)

self._options_button = wx.Button(parent, wx.ID_MORE, _OPTIONS_SHOWN_LABEL)
button_sizer.Add(
self._options_button,
flag=wx.CENTER|wx.LEFT,
border=_WINDOW_INNER_PADDING)

button_sizer.AddStretchSpacer()
button_sizer.Add(CreateButtonSizer(parent, ok_button_id, wx.ID_CANCEL), flag=wx.CENTER)
return button_sizer

# === Operations ===

def _update_preview_urls(self) -> None:
Expand Down Expand Up @@ -253,16 +408,18 @@ def _on_pattern_field_changed(self, event) -> None:
def _on_button(self, event: wx.CommandEvent) -> None:
btn_id = event.GetEventObject().GetId()
if btn_id in (wx.ID_NEW, wx.ID_SAVE):
self._on_ok(event)
self._on_ok()
elif btn_id == wx.ID_CANCEL:
self._on_cancel(event)
self._on_cancel()
elif btn_id == wx.ID_MORE:
self._on_options_toggle()

def _on_close(self, event: wx.CloseEvent) -> None:
self._on_cancel(event)
self._on_cancel()

def _on_ok(self, event: wx.CommandEvent) -> None:
name = self.name_field.GetValue()
url_pattern = self.pattern_field.GetValue()
def _on_ok(self) -> None:
name = self.name_field.Value
url_pattern = self.pattern_field.Value
source = self.source_choice_box.GetClientData(
self.source_choice_box.GetSelection())
if self._saving_source_would_create_cycle_func(source):
Expand All @@ -277,9 +434,37 @@ def _on_ok(self, event: wx.CommandEvent) -> None:
choice = ShowModal(dialog)
assert wx.ID_OK == choice
return
self._on_finish(name, url_pattern, source)
do_not_download = self.do_not_download_checkbox.Value
self._on_finish(name, url_pattern, source, do_not_download)

self.dialog.Destroy()

def _on_cancel(self, event: wx.CommandEvent) -> None:
def _on_cancel(self) -> None:
self.dialog.Destroy()

def _on_options_toggle(self) -> None:
options = self._options_sizer.GetStaticBox()
if options.Shown:
# Hide
self._options_button.Label = _OPTIONS_NOT_SHOWN_LABEL

options_height = options.Size.Height + _ABOVE_OPTIONS_PADDING
options.Shown = False
self.dialog.SetSize(
x=wx.DefaultCoord,
y=wx.DefaultCoord,
width=wx.DefaultCoord,
height=self.dialog.Size.Height - options_height,
sizeFlags=wx.SIZE_USE_EXISTING)
else:
# Show
self._options_button.Label = _OPTIONS_SHOWN_LABEL

options.Shown = True
options_height = options.Size.Height + _ABOVE_OPTIONS_PADDING
self.dialog.SetSize(
x=wx.DefaultCoord,
y=wx.DefaultCoord,
width=wx.DefaultCoord,
height=self.dialog.Size.Height + options_height,
sizeFlags=wx.SIZE_USE_EXISTING)
10 changes: 6 additions & 4 deletions src/crystal/browser/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ def __init__(self, parent: wx.Window, project: 'Project') -> None:
bind(dialog, wx.EVT_CLOSE, self._on_close)

project_box_sizer = wx.StaticBoxSizer(wx.VERTICAL, dialog, label='Project')
project_box_sizer.Add(self._create_project_fields(project_box_sizer.GetStaticBox()))
project_box_sizer.Add(wrap_static_box_sizer_child(
self._create_project_fields(project_box_sizer.GetStaticBox())))
dialog_sizer.Add(project_box_sizer, flag=wx.EXPAND|wx.ALL,
border=_WINDOW_INNER_PADDING)

session_box_sizer = wx.StaticBoxSizer(wx.VERTICAL, dialog, label='Session')
session_box_sizer.Add(self._create_session_fields(session_box_sizer.GetStaticBox()))
session_box_sizer.Add(wrap_static_box_sizer_child(
self._create_session_fields(session_box_sizer.GetStaticBox())))
dialog_sizer.Add(session_box_sizer, flag=wx.EXPAND|(wx.ALL & ~wx.TOP),
border=_WINDOW_INNER_PADDING)

Expand Down Expand Up @@ -90,7 +92,7 @@ def _create_project_fields(self, parent: wx.Window) -> wx.Sizer:
self.html_parser_field,
flag=wx.EXPAND, pos=wx.GBPosition(1, 1))

return wrap_static_box_sizer_child(fields_sizer)
return fields_sizer

def _create_session_fields(self, parent: wx.Window) -> wx.Sizer:
fields_sizer = wx.GridBagSizer(
Expand Down Expand Up @@ -125,7 +127,7 @@ def _create_session_fields(self, parent: wx.Window) -> wx.Sizer:
self.cookie_field,
flag=wx.EXPAND, pos=wx.GBPosition(4, 1))

return wrap_static_box_sizer_child(fields_sizer)
return fields_sizer

def _create_stale_before_field(self, parent: wx.Window) -> wx.Sizer:
import wx.adv # import late because does print spurious messages on macOS
Expand Down
1 change: 1 addition & 0 deletions src/crystal/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3161,6 +3161,7 @@ def __init__(self,
self.url_pattern = url_pattern
self._url_pattern_re = ResourceGroup.create_re_for_url_pattern(url_pattern)
self._source = None # type: Union[ResourceGroupSource, EllipsisType]
self.do_not_download = False

# Calculate members on demand rather than up front
self._members = None # type: Optional[List[Resource]]
Expand Down
Loading

0 comments on commit 50a60ad

Please sign in to comment.