Skip to content

Commit c074f74

Browse files
committed
- adding support for section renaming
1 parent 23dd749 commit c074f74

File tree

8 files changed

+273
-31
lines changed

8 files changed

+273
-31
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Notes application written in Python 3.8 & KivyMD
55

66
## application description
77
- OS independent
8-
- Notes can be grouped in separate sections, these sections can be further added and removed
8+
- Notes can be grouped in separate sections, these sections can be added, renamed or removed
99
- Notes storage file can be placed anywhere on the local drive but can also be placed in a DropBox shared folder for example to synchronize notes across devices
1010
- Full-text or word-based search capability across the current section or all sections
1111
- Customizable fonts and colors, settings can be persisted
@@ -112,13 +112,14 @@ change in the spec file the line datas=[], to datas=[("notes_app/view/notes_view
112112
```
113113

114114
## useful links
115-
- development
115+
- app development
116116
- https://kivymd.readthedocs.io/en/latest/components/
117117
- https://www.tutorialspoint.com/design_pattern/mvc_pattern.htm
118118
- https://github.com/HeaTTheatR/Kivy_MVC_Template/tree/main
119119
- https://github.com/thebjorn/pydeps
120+
- https://github.com/kivymd/KivyMD/blob/master/kivymd/icon_definitions.py
120121

121-
- app building
122+
- building the app
122123
- https://pyinstaller.org/en/stable/when-things-go-wrong.html
123124
- https://dev.to/ngonidzashe/using-pyinstaller-to-package-kivy-and-kivymd-desktop-apps-2fmj
124125
- https://github.com/devgiordane/kivy-md-build

notes_app/controller/notes_controller.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def save_file_data(self, data):
6868
before = f.read() or ""
6969
f.close()
7070

71+
# TODO try merge strings one section loaded from file at a time
7172
data = merge_strings(before=before, after=data)
7273

7374
f = open(self.model.file_path, "w")

notes_app/file.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,28 @@ def delete_all_sections_content(self) -> None:
131131
def delete_section_content(self, section_file_separator) -> None:
132132
self._data_by_sections.pop(section_file_separator)
133133

134+
def rename_section(
135+
self, old_section_file_separator, new_section_file_separator
136+
) -> None:
137+
"""
138+
rename_section method abstracts all section renaming actions
139+
by renaming the section identifier in the _section_identifiers list
140+
and by replacing the key in the _data_by_sections dict
141+
"""
142+
idx = 0
143+
for idx, section_identifier in enumerate(self._section_identifiers):
144+
if section_identifier.section_file_separator == old_section_file_separator:
145+
break
146+
147+
self._section_identifiers[idx] = SectionIdentifier(
148+
defaults=self.defaults, section_file_separator=new_section_file_separator
149+
)
150+
151+
self._data_by_sections[new_section_file_separator] = self._data_by_sections[
152+
old_section_file_separator
153+
]
154+
del self._data_by_sections[old_section_file_separator]
155+
134156
def _transform_raw_data_content_to_data_by_sections(
135157
self,
136158
) -> Dict[SectionIdentifier, AnyStr]:

notes_app/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.1"
1+
__version__ = "0.1.0"

notes_app/view/notes_view.kv

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,22 @@
1919
text: root.text
2020
theme_text_color: "Custom"
2121
on_release: self.parent.set_color_item(self)
22+
on_size:
23+
self.ids._right_container.width = icons_container.width
24+
self.ids._right_container.x = icons_container.width
2225

23-
IconLeftWidget:
24-
id: icon
25-
icon: root.icon
26-
theme_text_color: "Custom"
26+
IconsContainer:
27+
id: icons_container
28+
29+
MDIconButton:
30+
icon: 'file-document-edit-outline'
31+
on_release: root.edit(section_item)
32+
theme_text_color: "Custom"
2733

28-
IconRightWidget:
29-
icon: 'trash-can-outline'
30-
on_release: root.delete(section_item)
31-
theme_text_color: "Custom"
34+
MDIconButton:
35+
icon: 'trash-can-outline'
36+
on_release: root.delete(section_item)
37+
theme_text_color: "Custom"
3238

3339
<NotesView>:
3440
orientation: "vertical"
@@ -219,7 +225,7 @@
219225

220226
MDTextField:
221227
id: section_name_input_value
222-
hint_text: "What section name to add?"
228+
hint_text: "Enter section name"
223229
max_text_length: 20
224230

225231
BoxLayout:
@@ -231,5 +237,39 @@
231237
on_release: root.cancel()
232238
MDRectangleFlatButton:
233239
size_hint: (.5, .8)
234-
text: "Add"
240+
text: "Save"
235241
on_release: root.execute_add_section(section_name_input_value.text)
242+
243+
<EditSectionDialogContent>
244+
orientation: "vertical"
245+
spacing: "12dp"
246+
size_hint_y: None
247+
height: "500dp"
248+
249+
MDBoxLayout:
250+
size: root.size
251+
pos: root.pos
252+
orientation: "vertical"
253+
254+
MDLabel:
255+
font_style: "H6"
256+
halign: "center"
257+
valign: "center"
258+
text: root.edit_section_result_message
259+
260+
MDTextField:
261+
id: section_name_input_value
262+
hint_text: "Enter new section name"
263+
max_text_length: 20
264+
265+
BoxLayout:
266+
size_hint_y: None
267+
height: 40
268+
MDFlatButton:
269+
size_hint: (.5, .8)
270+
text: "Cancel"
271+
on_release: root.cancel()
272+
MDRectangleFlatButton:
273+
size_hint: (.5, .8)
274+
text: "Save"
275+
on_release: root.execute_edit_section(root.old_section_name, section_name_input_value.text)

notes_app/view/notes_view.py

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
from kivymd.uix.boxlayout import MDBoxLayout
1414
from kivymd.uix.dialog import MDDialog
1515
from kivymd.uix.filemanager import MDFileManager
16-
from kivymd.uix.list import MDList, OneLineAvatarIconListItem, ThreeLineListItem
16+
from kivymd.uix.list import (
17+
MDList,
18+
OneLineAvatarIconListItem,
19+
ThreeLineListItem,
20+
IRightBodyTouch,
21+
)
1722
from kivymd.uix.menu import MDDropdownMenu
1823
from kivymd.uix.screen import MDScreen
1924
from kivymd.uix.snackbar import BaseSnackbar
@@ -56,11 +61,15 @@
5661
EXTERNAL_REPOSITORY_URL = "https://www.github.com/datahappy1/notes_app/"
5762

5863

64+
class IconsContainer(IRightBodyTouch, MDBoxLayout):
65+
pass
66+
67+
5968
class ItemDrawer(OneLineAvatarIconListItem):
60-
icon = StringProperty()
61-
id = StringProperty()
62-
text = StringProperty()
63-
delete = ObjectProperty()
69+
id = StringProperty(None)
70+
text = StringProperty(None)
71+
edit = ObjectProperty(None)
72+
delete = ObjectProperty(None)
6473

6574

6675
class ContentNavigationDrawer(MDBoxLayout):
@@ -101,6 +110,13 @@ class AddSectionDialogContent(MDBoxLayout):
101110
cancel = ObjectProperty(None)
102111

103112

113+
class EditSectionDialogContent(MDBoxLayout):
114+
old_section_name = StringProperty(None)
115+
edit_section_result_message = StringProperty(None)
116+
execute_edit_section = ObjectProperty(None)
117+
cancel = ObjectProperty(None)
118+
119+
104120
class SearchDialogContent(MDBoxLayout):
105121
get_search_switch_state = ObjectProperty(None)
106122
search_switch_callback = ObjectProperty(None)
@@ -216,12 +232,12 @@ def set_drawer_items(self, section_identifiers):
216232
for section_identifier in section_identifiers:
217233
self.ids.md_list.add_widget(
218234
ItemDrawer(
219-
icon="bookmark-outline",
220235
id=section_identifier.section_file_separator,
221-
text=f"section: {section_identifier.section_name}",
236+
text=section_identifier.section_name,
222237
on_release=lambda x=f"{section_identifier.section_file_separator}": self.press_drawer_item_callback(
223238
x
224239
),
240+
edit=self.press_edit_section,
225241
delete=self.press_delete_section,
226242
)
227243
)
@@ -488,24 +504,21 @@ def execute_search(self, *args):
488504
)
489505

490506
def execute_add_section(self, *args):
507+
section_name = args[0]
508+
491509
if (
492-
not args[0]
493-
or len(args[0]) < SECTION_FILE_NAME_MINIMAL_CHAR_COUNT
494-
or args[0].isspace()
495-
or args[0] in [si.section_name for si in self.file.section_identifiers]
510+
not section_name
511+
or len(section_name) < SECTION_FILE_NAME_MINIMAL_CHAR_COUNT
512+
or section_name.isspace()
513+
or section_name in [si.section_name for si in self.file.section_identifiers]
496514
):
497515
self.dialog.content_cls.add_section_result_message = "Invalid name"
498516
return
499517

500-
section_name = args[0]
501518
section_identifier = SectionIdentifier(
502519
defaults=self.defaults, section_name=section_name
503520
)
504521

505-
if section_identifier.section_file_separator in self.file.section_identifiers:
506-
self.dialog.content_cls.add_section_result_message = "Name already exists"
507-
return
508-
509522
self.file.add_section_identifier(
510523
section_file_separator=section_identifier.section_file_separator
511524
)
@@ -520,6 +533,39 @@ def execute_add_section(self, *args):
520533

521534
self.cancel_dialog()
522535

536+
def execute_edit_section(self, *args):
537+
old_section_name, new_section_name = args
538+
539+
if (
540+
not new_section_name
541+
or len(new_section_name) < SECTION_FILE_NAME_MINIMAL_CHAR_COUNT
542+
or new_section_name.isspace()
543+
or new_section_name
544+
in [si.section_name for si in self.file.section_identifiers]
545+
or old_section_name == new_section_name
546+
):
547+
self.dialog.content_cls.edit_section_result_message = "Invalid name"
548+
return
549+
550+
new_section_identifier = SectionIdentifier(
551+
defaults=self.defaults, section_name=new_section_name
552+
)
553+
554+
old_section_identifier = SectionIdentifier(
555+
defaults=self.defaults, section_name=old_section_name
556+
)
557+
558+
self.file.rename_section(
559+
old_section_file_separator=old_section_identifier.section_file_separator,
560+
new_section_file_separator=new_section_identifier.section_file_separator,
561+
)
562+
563+
self.filter_data_split_by_section(section_identifier=new_section_identifier)
564+
565+
self.set_drawer_items(section_identifiers=self.file.section_identifiers)
566+
567+
self.cancel_dialog()
568+
523569
def execute_goto_external_url(self):
524570
return webbrowser.open(EXTERNAL_REPOSITORY_URL)
525571

@@ -592,6 +638,23 @@ def press_add_section(self, *args):
592638
self.dialog = MDDialog(title="Add section:", type="custom", content_cls=content)
593639
self.dialog.open()
594640

641+
def press_edit_section(self, section_item):
642+
section_name = SectionIdentifier(
643+
section_file_separator=section_item.id, defaults=self.defaults
644+
).section_name
645+
646+
content = EditSectionDialogContent(
647+
old_section_name=section_name,
648+
edit_section_result_message="",
649+
execute_edit_section=self.execute_edit_section,
650+
cancel=self.cancel_dialog,
651+
)
652+
653+
self.dialog = MDDialog(
654+
title=f"Edit section {section_name}:", type="custom", content_cls=content
655+
)
656+
self.dialog.open()
657+
595658
def press_delete_section(self, section_item):
596659
if len(self.file.section_identifiers) == 1:
597660
self.show_error_bar(error_message="Cannot delete last section")

tests/test_unit_file.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,37 @@ def test_delete_all_sections_content(self, get_file):
141141
assert not get_file.delete_all_sections_content()
142142
assert not get_file.transform_data_by_sections_to_raw_data_content()
143143

144+
def test_rename_section(self, get_file):
145+
assert (
146+
get_file.add_section_identifier(section_file_separator="<section=a> ")
147+
is None
148+
)
149+
150+
assert not get_file.set_section_content(
151+
section_file_separator="<section=a> ", section_content="some content"
152+
)
153+
154+
assert [si.section_name for si in get_file._section_identifiers] == [
155+
"first",
156+
"second",
157+
"a",
158+
]
159+
160+
assert not get_file.rename_section(
161+
old_section_file_separator="<section=a> ",
162+
new_section_file_separator="<section=b> ",
163+
)
164+
assert (
165+
get_file.get_section_content(section_file_separator="<section=b> ")
166+
== "some content"
167+
)
168+
169+
assert [si.section_name for si in get_file._section_identifiers] == [
170+
"first",
171+
"second",
172+
"b",
173+
]
174+
144175
def test__transform_raw_data_content_to_data_by_sections(self, get_file):
145176
assert get_file._transform_raw_data_content_to_data_by_sections() == {
146177
"<section=first> ": "Quod equidem non reprehendo\n",

0 commit comments

Comments
 (0)