-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaccel_map.go
274 lines (248 loc) · 8.95 KB
/
accel_map.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// Copyright (c) 2023 The Go-Curses Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ctk
import (
"fmt"
"regexp"
"strings"
"github.com/gofrs/uuid"
"github.com/go-curses/cdk"
"github.com/go-curses/cdk/lib/sync"
"github.com/go-curses/cdk/log"
)
const TypeAccelMap cdk.CTypeTag = "ctk-accel-map"
var (
ctkAccelMaps = make(map[uuid.UUID]*CAccelMap)
ctkAccelMapsLock = &sync.RWMutex{}
)
func init() {
_ = cdk.TypesManager.AddType(TypeAccelMap, nil)
}
// AccelMap Hierarchy:
// Object
// +- AccelMap
//
// The AccelMap is the global accelerator mapping object. There is only one
// AccelMap instance for any given Display.
type AccelMap interface {
Object
Init() (already bool)
AddEntry(accelPath string, accelKey cdk.Key, accelMods cdk.ModMask)
LookupEntry(accelPath string) (accelerator Accelerator, ok bool)
ChangeEntry(accelPath string, accelKey cdk.Key, accelMods cdk.ModMask, replace bool) (ok bool)
Load(fileName string)
LoadFromString(accelMap string)
Save(fileName string)
SaveToString() (accelMap string)
LockPath(accelPath string)
UnlockPath(accelPath string)
}
// The CAccelMap structure implements the AccelMap interface and is exported to
// facilitate type embedding with custom implementations. No member variables
// are exported as the interface methods are the only intended means of
// interacting with AccelMap objects.
type CAccelMap struct {
CObject
accelerators map[string]Accelerator
}
// GetAccelMap is the getter for the current Application AccelMap singleton.
// Returns nil if there is no Application present for the current thread.
func GetAccelMap() AccelMap {
if acd, err := cdk.GetLocalContext(); err != nil {
log.Error(err)
} else {
if app, ok := acd.Data.(Application); ok {
return app.AccelMap()
}
}
return nil
}
// Init initializes an AccelMap object. This must be called at least once to
// set up the necessary defaults and allocate any memory structures. Calling
// this more than once is safe though unnecessary. Only the first call will
// result in any effect upon the AccelMap instance. Init is used in the
// NewAccelMap constructor and only necessary when implementing a derivative
// AccelMap type.
func (a *CAccelMap) Init() (already bool) {
if a.InitTypeItem(TypeAccelMap, a) {
return true
}
a.CObject.Init()
a.accelerators = make(map[string]Accelerator)
return false
}
// AddEntry registers a new accelerator with the global accelerator map. This
// method should only be called once per accel_path with the canonical accel_key
// and accel_mods for this path. To change the accelerator during runtime
// programmatically, use AccelMap.ChangeEntry(). The accelerator path must
// consist of "<WINDOWTYPE>/Category1/Category2/.../Action", where <WINDOWTYPE>
// should be a unique application-specific identifier, that corresponds to the
// kind of window the accelerator is being used in, e.g. "Gimp-Image",
// "Abiword-Document" or "Gnumeric-Settings". The Category1/.../Action portion
// is most appropriately chosen by the action the accelerator triggers, i.e. for
// accelerators on menu items, choose the item's menu path, e.g. "File/Save As",
// "Image/View/Zoom" or "Edit/Select All". So a full valid accelerator path may
// look like: "<Gimp-Toolbox>/File/Dialogs/Tool Options...".
//
// Parameters
// accel_path valid accelerator path
// accel_key the accelerator key
// accel_mods the accelerator modifiers
func (a *CAccelMap) AddEntry(accelPath string, accelKey cdk.Key, accelMods cdk.ModMask) {
a.RLock()
if _, ok := a.accelerators[accelPath]; ok {
a.RUnlock()
log.ErrorDF(1, "accelerator exists for path: %v", accelPath)
return
}
a.RUnlock()
a.Lock()
a.accelerators[accelPath] = NewAccelerator(accelPath, accelKey, accelMods)
a.Unlock()
}
// LookupEntry returns the accelerator entry for accel_path.
//
// Parameters
// accel_path a valid accelerator path
func (a *CAccelMap) LookupEntry(accelPath string) (accelerator Accelerator, ok bool) {
a.RLock()
if accelerator, ok = a.accelerators[accelPath]; !ok {
accelerator = nil
}
a.RUnlock()
return
}
// ChangeEntry updates the accel_key and accel_mods currently associated with
// accel_path. Due to conflicts with other accelerators, a change may not always
// be possible, replace indicates whether other accelerators may be deleted to
// resolve such conflicts. A change will only occur if all conflicts could be
// resolved (which might not be the case if conflicting accelerators are
// locked). Successful changes are indicated by a TRUE return value.
//
// Parameters
// accel_path a valid accelerator path
// accel_key the new accelerator key
// accel_mods the new accelerator modifiers
// replace TRUE if other accelerators may be deleted upon conflicts
func (a *CAccelMap) ChangeEntry(accelPath string, accelKey cdk.Key, accelMods cdk.ModMask, replace bool) (ok bool) {
// get the Accelerator for accelPath
// find any other association of accelKey + accelMods
// if the found is locked and not replace, nok
// else if the accelPath is locked, nok
var foundOtherAccel Accelerator
for _, accel := range a.accelerators {
if accel.Match(accelKey, accelMods) {
foundOtherAccel = accel
break
}
}
if foundOtherAccel != nil {
if foundOtherAccel.IsLocked() && !replace {
// existing key+mods assignment is treated immutable
return false
}
// unset the existing key+mods assignment
foundOtherAccel.UnsetKeyMods()
}
if foundPathAccel, pok := a.accelerators[accelPath]; pok {
foundPathAccel.Configure(accelKey, accelMods)
return true
}
a.LogError("accelPath not found: %v", accelPath)
return false
}
// Load parses a file previously saved with AccelMap.Save() for accelerator
// specifications, and propagates them accordingly.
//
// Parameters
// file_name a file containing accelerator specifications
func (a *CAccelMap) Load(fileName string) {
}
var rxAccelMapLineParser = regexp.MustCompile(`^\s*<([^>]+?)>/((?:[A-Z][- a-zA-Z\d]+?[a-zA-Z-\d]|/)+)\s*=\s*(.+?)\s*$`)
func (a *CAccelMap) LoadFromString(accelMap string) {
parsed := make(map[string]string)
for _, line := range strings.Split(accelMap, "\n") {
line = strings.TrimSpace(line)
if rxAccelMapLineParser.MatchString(line) {
m := rxAccelMapLineParser.FindAllStringSubmatch(line, -1)
if len(m) == 1 && len(m[0]) == 4 {
p := fmt.Sprintf("<%s>/%s", m[0][1], m[0][2])
parsed[p] = m[0][3]
}
}
}
for path, keyMods := range parsed {
if key, mods, err := cdk.ParseKeyMods(keyMods); err != nil {
a.LogErr(err)
} else {
a.Lock()
if accelerator, ok := a.accelerators[path]; ok {
accelerator.Configure(key, mods)
} else {
a.accelerators[path] = NewAccelerator(path, key, mods)
}
a.Unlock()
}
}
}
// Save stores the current accelerator specifications (accelerator path, key and
// modifiers) to file_name. The file is written in a format suitable to be read
// back in by AccelMap.Load().
//
// Parameters
// file_name the name of the file to contain accelerator specifications
func (a *CAccelMap) Save(fileName string) {
}
func (a *CAccelMap) SaveToString() (accelMap string) {
a.RLock()
for path, accelerator := range a.accelerators {
_, key, mods := accelerator.Settings()
accelMap += fmt.Sprintf("%v = %v%v\n", path, mods.String(), key)
}
a.RUnlock()
return
}
// LockPath locks the given accelerator path. If the accelerator map doesn't yet
// contain an entry for accel_path, a new one is created.
//
// Locking an accelerator path prevents its accelerator from being changed
// during runtime. A locked accelerator path can be unlocked by
// AccelMap.UnlockPath(). Refer to AccelMap.ChangeEntry() for information about
// runtime accelerator changes.
//
// If called more than once, accel_path remains locked until
// AccelMap.UnlockPath() has been called an equivalent number of times.
//
// Note that locking of individual accelerator paths is independent of locking
// the AccelGroup containing them. For runtime accelerator changes to be
// possible both the accelerator path and its AccelGroup have to be unlocked.
//
// Parameters
// accel_path a valid accelerator path
func (a *CAccelMap) LockPath(accelPath string) {
a.Lock()
if accelerator, ok := a.accelerators[accelPath]; !ok {
a.accelerators[accelPath] = NewDefaultAccelerator(accelPath)
} else {
accelerator.Lock()
}
a.Unlock()
}
// UnlockPath undoes the last call to AccelMap.LockPath() on this accel_path.
// Refer to AccelMap.LockPath() for information about accelerator path locking.
//
// Parameters
// accel_path a valid accelerator path
func (a *CAccelMap) UnlockPath(accelPath string) {
}