blob: ba6ca3787a19f1f3df34e07b447f2aa53063583b [file] [log] [blame]
Philipp Le6206b9c2022-04-26 22:14:03 +02001# SPDX-License-Identifier: MPL-2.0
2# Copyright (c) 2022 Philipp Le <philipp@philipple.de>.
3# This Source Code Form is subject to the terms of the Mozilla Public
4# License, v. 2.0. If a copy of the MPL was not distributed with this
5# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7
8from typing import Type, List, Callable, AnyStr, Optional
9from tkinter import ttk, Listbox, X, LEFT, ACTIVE
10from tkinter.simpledialog import Dialog
11from pydantic import BaseModel
12from ..store import ConfigObject
13
14from .base import make_config_widget_base
15
16
17Entry2StrCb = Callable[[ConfigObject], AnyStr]
18
19
20class ListBoxWidget(make_config_widget_base(ttk.Frame)):
21 MODEL_TYPE = list
22 FLAVOUR = 'list'
23
24 def __init__(self, model: BaseModel, model_key: str, tp: Type, parent, translate_cb: Entry2StrCb, *args, **kwargs):
25 super().__init__(model, model_key, tp, parent, *args, **kwargs)
26
27 self.translate_cb: Entry2StrCb = translate_cb
28
29 self._box = Listbox(self)
30 self._box.bind('<Double-Button-1>', self._on_double_click)
31 self._box.pack(fill=X)
32
33 self._add_btn = ttk.Button(self, text='+ Add', command=self._on_add)
34 self._add_btn.pack(side=LEFT, padx=5, pady=5)
35
36 self._remove_btn = ttk.Button(self, text='- Remove', command=self._on_remove)
37 self._remove_btn.pack(side=LEFT, padx=5, pady=5)
38
39 self._up_btn = ttk.Button(self, text='^ Up', command=self._on_up)
40 self._up_btn.pack(side=LEFT, padx=5, pady=5)
41
42 self._down_btn = ttk.Button(self, text='v Down', command=self._on_down)
43 self._down_btn.pack(side=LEFT, padx=5, pady=5)
44
45 def sync_out(self) -> None:
46 self.notify_change()
47
48 def sync_in(self) -> None:
49 self._box.delete(0, self._box.size())
50
51 l = getattr(self.model, self.model_key)
52 for e in l:
53 self._box.insert('end', self.translate_cb(e))
54
55 def _config_widget(self):
56 self.sync_in()
57
58 def _run_dialog(self, index: Optional[int]):
59 l: List[ConfigObject] = getattr(self.model, self.model_key)
60 tp = self.type_
61
62 class EditDialog(Dialog):
63 def __init__(self, index, *args, **kwargs):
64 self.index = index
65 super().__init__(*args, **kwargs)
66
67 def body(self, master) -> None:
68 if self.index is not None:
69 self.item = None
70 w = l[self.index].make_config_widget(master)
71 else:
72 self.item = tp()
73 w = self.item.make_config_widget(master)
74 w.pack()
75
76 def buttonbox(self):
77 box = ttk.Frame(self)
78
79 w = ttk.Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
80 w.pack(side=LEFT, padx=5, pady=5)
81
82 self.bind("<Return>", self.ok)
83
84 if self.index is None:
85 w = ttk.Button(box, text="Cancel", width=10, command=self.cancel)
86 w.pack(side=LEFT, padx=5, pady=5)
87
88 self.bind("<Escape>", self.cancel)
89
90 box.pack()
91
92 def apply(self):
93 if self.index is None:
94 l.append(self.item)
95
96 EditDialog(index, self)
97 setattr(self.model, self.model_key, l)
98 self.notify_change()
99 self.sync_in()
100
101 def _on_add(self):
102 self._run_dialog(None)
103
104 last = len(getattr(self.model, self.model_key))
105 self._box.selection_set(last - 1)
106 self._box.see(last - 1)
107 self._box.activate(last - 1)
108 self._box.selection_anchor(last - 1)
109
110 def _on_double_click(self, _):
111 index = self._box.curselection()
112 if len(index) > 0:
113 self._run_dialog(index[0])
114
115 def _on_remove(self):
116 l: List[ConfigObject] = getattr(self.model, self.model_key)
117
118 index = self._box.curselection()
119 if len(index) > 0:
120 l.pop(index[0])
121
122 setattr(self.model, self.model_key, l)
123 self.notify_change()
124 self.sync_in()
125
126 self._box.selection_set(index[0] - 1)
127 self._box.see(index[0] - 1)
128 self._box.activate(index[0] - 1)
129 self._box.selection_anchor(index[0] - 1)
130
131 def _on_up(self):
132 l: List[ConfigObject] = getattr(self.model, self.model_key)
133
134 index = self._box.curselection()
135 if len(index) > 0 and index[0] > 0:
136 l.insert(index[0] - 1, l.pop(index[0]))
137
138 setattr(self.model, self.model_key, l)
139 self.sync_in()
140 self.notify_change()
141
142 self._box.selection_set(index[0] - 1)
143 self._box.see(index[0] - 1)
144 self._box.activate(index[0] - 1)
145 self._box.selection_anchor(index[0] - 1)
146
147 def _on_down(self):
148 l: List[ConfigObject] = getattr(self.model, self.model_key)
149
150 index = self._box.curselection()
151 if len(index) > 0 and index[0] < len(l):
152 l.insert(index[0] + 1, l.pop(index[0]))
153
154 setattr(self.model, self.model_key, l)
155 self.sync_in()
156 self.notify_change()
157 self._box.selection_set(index[0] + 1)
158 self._box.see(index[0] + 1)
159 self._box.activate(index[0] + 1)
160 self._box.selection_anchor(index[0] + 1)