forked from diffpy/diffpy.pdfgui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparameterspanel.py
484 lines (401 loc) · 17 KB
/
parameterspanel.py
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
##############################################################################
#
# PDFgui by DANSE Diffraction group
# Simon J. L. Billinge
# (c) 2006 trustees of the Michigan State University.
# All rights reserved.
#
# File coded by: Dmitriy Bryndin, Chris Farrow
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
##############################################################################
# Parameters panel
# Dmitriy Bryndin
# generated by wxGlade 0.9.3 on Fri Jul 19 16:04:38 2019
import wx.grid
from diffpy.pdfgui.gui.pdfpanel import PDFPanel
from diffpy.pdfgui.gui.wxextensions import wx12
from diffpy.pdfgui.gui.wxextensions.autowidthlabelsgrid import AutoWidthLabelsGrid
from diffpy.utils.wx import gridutils
class ParametersPanel(wx.Panel, PDFPanel):
"""GUI Panel, parameters viewer/editor
Data members:
parameters -- parameters dictionary
_focusedText -- value of a cell before it changes
"""
def __init__(self, *args, **kwds):
PDFPanel.__init__(self)
# begin wxGlade: ParametersPanel.__init__
kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
wx.Panel.__init__(self, *args, **kwds)
self.grid_parameters = AutoWidthLabelsGrid(self, wx.ID_ANY, size=(1, 1))
self.button_applyparameters = wx.Button(self, wx.ID_ANY, "Apply parameters")
self.__set_properties()
self.__do_layout()
self.Bind(wx.grid.EVT_GRID_CMD_CELL_CHANGED, self.onCellChange, self.grid_parameters)
self.Bind(
wx.grid.EVT_GRID_CMD_CELL_LEFT_CLICK,
self.onCellLeftClick,
self.grid_parameters,
)
self.Bind(
wx.grid.EVT_GRID_CMD_CELL_RIGHT_CLICK,
self.onCellRightClick,
self.grid_parameters,
)
self.Bind(wx.grid.EVT_GRID_CMD_EDITOR_SHOWN, self.onEditorShown, self.grid_parameters)
self.Bind(
wx.grid.EVT_GRID_CMD_RANGE_SELECT,
self.onGridRangeSelect,
self.grid_parameters,
)
self.Bind(wx.EVT_BUTTON, self.onApplyParameters, self.button_applyparameters)
# end wxGlade
self.__customProperties()
return
def __set_properties(self):
# begin wxGlade: ParametersPanel.__set_properties
self.grid_parameters.CreateGrid(0, 3)
self.grid_parameters.EnableDragRowSize(0)
self.grid_parameters.SetColLabelValue(0, "Initial")
self.grid_parameters.SetColLabelValue(1, "Fixed")
self.grid_parameters.SetColLabelValue(2, "Refined")
# end wxGlade
# set the second column to display boolean values
attr = wx.grid.GridCellAttr()
attr.SetEditor(wx.grid.GridCellBoolEditor())
attr.SetRenderer(wx.grid.GridCellBoolRenderer())
self.grid_parameters.SetColAttr(1, attr)
def __do_layout(self):
# begin wxGlade: ParametersPanel.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.grid_parameters, 1, wx.EXPAND, 0)
sizer_buttons.Add((20, 20), 1, 0, 0)
sizer_buttons.Add(self.button_applyparameters, 0, wx.ALL, 5)
sizer_1.Add(sizer_buttons, 0, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.Layout()
# end wxGlade
# ########################################################################
# Misc Methods
def __customProperties(self):
"""Custom properties for the panel."""
self._focusedText = None
self._selectedCells = []
self.parameters = {}
self.fit = None
return
def refresh(self):
"""Refreshes wigets on the panel"""
# # Update the parameters dictionary
# self.fitting.updateParameters()
nRows = len(self.parameters)
# update the grid
# remove all rows and create new ones
self.grid_parameters.BeginBatch()
gridrows = self.grid_parameters.GetNumberRows()
if gridrows != 0:
self.grid_parameters.DeleteRows(numRows=gridrows)
self.grid_parameters.InsertRows(numRows=nRows)
# self.grid_parameters.SetColFormatBool(1)
i = 0
keys = sorted(self.parameters.keys())
for key in keys:
# parameter index
self.grid_parameters.SetRowLabelValue(i, str(self.parameters[key].idx))
# initial value
self.grid_parameters.SetCellValue(i, 0, str(self.parameters[key].initialStr()))
# flag "fixed"
# NOTE: for bool type of cells use '0' or '1' as False and True
self.grid_parameters.SetCellValue(i, 1, str(int(self.parameters[key].fixed)))
# refined value
self.grid_parameters.SetReadOnly(i, 2)
if self.parameters[key].refined is None:
self.grid_parameters.SetCellValue(i, 2, "")
else:
self.grid_parameters.SetCellValue(i, 2, str(self.parameters[key].refined))
i += 1
self.grid_parameters.AutosizeLabels()
self.grid_parameters.AutoSizeColumns()
self.grid_parameters.EndBatch()
def onCellLeftClick(self, event): # wxGlade: ParametersPanel.<event_handler>
"""Toggle a fix/free cell when clicked."""
r = event.GetRow()
c = event.GetCol()
# Only proceed if there is no selection and
# this click has no keyboard modifiers.
ignorethis = c != 1 or self.grid_parameters.IsSelection() or event.ShiftDown() or event.ControlDown()
if ignorethis:
# do standard click event handling
event.Skip()
return
# We consume the event here. This prevents focusing the clicked
# cell after a click, but that is not necessary for a checkbox.
state = int(self.grid_parameters.GetCellValue(r, c) or 0)
self.applyCellChange(r, c, not state)
return
def onGridRangeSelect(self, event): # wxGlade: ParametersPanel.<event_handler>
"""Handle range selections.
This is needed to properly handle simple left-clicking of fix/free
cells. It serves no other purpose.
"""
event.Skip()
return
def onCellRightClick(self, event): # wxGlade: ParametersPanel.<event_handler>
# Popup the menu. If an item is selected then its handler
# will be called before PopupMenu returns.
r = event.GetRow()
c = event.GetCol()
# If the right-clicked node is not part of a group, then make sure that
# it is the only selected cell.
append = False
if self.grid_parameters.IsInSelection(r, c):
append = True
self.grid_parameters.SelectBlock(r, c, r, c, append)
self.popupMenu(self.grid_parameters, event.GetPosition().x, event.GetPosition().y)
event.Skip()
return
def onEditorShown(self, event): # wxGlade: ParametersPanel.<event_handler>
i = event.GetRow()
j = event.GetCol()
self._focusedText = self.grid_parameters.GetCellValue(i, j)
self._selectedCells = gridutils.getSelectedCells(self.grid_parameters)
event.Skip()
return
def onCellChange(self, event): # wxGlade: ParametersPanel.<event_handler>
# NOTE: be careful with refresh() => recursion! operations on grid will
# call onCellChange
# Note that this method does not get called when a fix/free cell is
# selected.
i = event.GetRow()
j = event.GetCol()
if self._focusedText is None:
return
self._focusedText = None
value = self.grid_parameters.GetCellValue(i, j)
# Verify the value. This is done here since if it is allowed to be done
# in fillCells, then an error dialog will be thrown for each point
# in the loop.
try:
# Check that the value is valid
if j == 0:
converted = value
try:
converted = int(value)
except ValueError:
pass
key = int(self.grid_parameters.GetRowLabelValue(i))
temp = self.parameters[key].initialValue()
if temp != converted:
self.parameters[key].setInitial(converted)
self.mainFrame.needsSave()
# If we made it this far, then we can continue.
self.fillCells(self._selectedCells, value)
self.grid_parameters.AutoSizeColumns(0)
finally:
# self.refresh()
event.Skip()
return
def applyCellChange(self, row, col, value):
"""Update parameters dictionary according to a change in a cell.
This also updates the cell, if possible, but not the grid. Changes to
the cell that may affect the grid, such as inserting text that is wider
than the column width, must be handled elsewhere.
row -- row
col -- column
value -- new value
"""
key = int(self.grid_parameters.GetRowLabelValue(row))
if col == 0: # initial value
temp = self.parameters[key].initialValue()
if temp != value:
self.parameters[key].setInitial(value)
self.grid_parameters.SetCellValue(row, 0, str(float(value)))
self.mainFrame.needsSave()
elif col == 1: # flag "fixed"
temp = bool(self.parameters[key].fixed)
value = bool(int(value))
if temp is not value:
self.parameters[key].fixed = value
self.grid_parameters.SetCellValue(row, 1, str(int(value)))
self.mainFrame.needsSave()
return
def popupMenu(self, window, x, y):
"""Opens a popup menu
window -- window, where to popup a menu
x -- x coordinate
y -- y coordinate
"""
# only do this part the first time so the events are only bound once
if not hasattr(self, "did_popupIDs"):
self.did_popupIDs = True
self.popupID1 = wx12.NewIdRef()
self.popupID2 = wx12.NewIdRef()
self.popupID3 = wx12.NewIdRef()
self.Bind(wx.EVT_MENU, self.onPopupFixFree, id=self.popupID1)
self.Bind(wx.EVT_MENU, self.onPopupCopyRefinedToInitial, id=self.popupID2)
self.Bind(wx.EVT_MENU, self.onPopupRenameParameters, id=self.popupID3)
# make a menu
menu = wx.Menu()
# add some other items
menu.Append(self.popupID1, "Fix / Free")
menu.Append(self.popupID2, "Copy Refined To Initial")
menu.Append(self.popupID3, "Rename Parameters")
# Popup the menu. If an item is selected then its handler
# will be called before PopupMenu returns.
window.PopupMenu(menu, wx.Point(x, y))
menu.Destroy()
return
# Popup menu events ##################################################
def onPopupFill(self, event):
"""Fills cells selected in the grid with a new value"""
# NOTE: GetSelectedCells returns only SINGLE selected cells, not blocks or row/columns !
if self.grid_parameters.IsSelection():
dlg = wx.TextEntryDialog(self, "New value:", "Fill Selected Cells", "")
if dlg.ShowModal() == wx.ID_OK:
value = dlg.GetValue()
rows = self.grid_parameters.GetNumberRows()
cols = self.grid_parameters.GetNumberCols()
for i in range(rows):
for j in range(cols):
inSelection = self.grid_parameters.IsInSelection(i, j)
valueChanged = value != self.grid_parameters.GetCellValue(i, j)
if inSelection and valueChanged:
self.applyCellChange(i, j, value)
# self.refresh()
dlg.Destroy()
event.Skip()
def onPopupFixFree(self, event):
"""Fixes parameters with selected cells"""
# NOTE: GetSelectedCells returns only SINGLE selected cells, not blocks
# or row/columns !
seldict = {}
if self.grid_parameters.IsSelection():
indices = self.getSelectedParameters()
for row in indices:
state = self.grid_parameters.GetCellValue(row, 1)
state = bool(int(state.strip() or "0"))
seldict[row] = state
# Find the majority state
nfixed = sum(1 for st in seldict.values() if st)
nfree = len(seldict) - nfixed
newstate = True # fixed
if nfree < nfixed:
# free all parameters
newstate = False
for row in seldict:
self.applyCellChange(row, 1, newstate)
event.Skip()
return
def onPopupCopyRefinedToInitial(self, event):
"""Copy refined parameter to initial value."""
if not self.grid_parameters.IsSelection():
event.Skip()
return
for row in self.getSelectedParameters():
refined = self.grid_parameters.GetCellValue(row, 2)
if refined == "":
continue
self.applyCellChange(row, 0, refined)
# Resize the first column
self.grid_parameters.AutoSizeColumn(0)
event.Skip()
return
def onPopupRenameParameters(self, event):
"""Rename parameters."""
if self.grid_parameters.IsSelection():
dlg = wx.TextEntryDialog(self, "New index:", "Rename Selected Parameters", "")
value = None
if dlg.ShowModal() == wx.ID_OK:
value = dlg.GetValue()
dlg.Destroy()
try:
value = int(value)
except (ValueError, TypeError):
return
rows = self.grid_parameters.GetNumberRows()
cols = self.grid_parameters.GetNumberCols()
selpars = []
# Get the selected parameters
for i in range(rows):
key = int(self.grid_parameters.GetRowLabelValue(i))
for j in range(cols):
if self.grid_parameters.IsInSelection(i, j):
selpars.append(key)
break
for key in selpars:
if key != value:
self.fit.changeParameterIndex(key, value)
self.mainFrame.needsSave()
self.fit.updateParameters()
self.refresh()
event.Skip()
return
# end of Popup menu events ###########################################
def onApplyParameters(self, event): # wxGlade: ParametersPanel.<event_handler>
self.fit.applyParameters()
self.mainFrame.needsSave()
event.Skip()
# Required by event handlers
def getSelectedParameters(self):
"""Get list of row values of selected cells."""
rows = self.grid_parameters.GetNumberRows()
cols = self.grid_parameters.GetNumberCols()
selection = []
for i in range(rows):
for j in range(cols):
if self.grid_parameters.IsInSelection(i, j):
selection.append(i)
break
return selection
def fillCells(self, indices, value):
"""Fill cells with a given value.
indices -- list of (i,j) tuples representing cell coordinates
value -- string value to place into cells
"""
for i, j in indices:
if j != 1 and not self.grid_parameters.IsReadOnly(i, j):
self.applyCellChange(i, j, value)
return
# end of class ParametersPanel
# testing code ############################################################
if __name__ == "__main__":
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.window = ParametersPanel(self, -1)
self.SetTitle("testing")
# choke, mainframe.needsSave() emulation
self.window.mainFrame = self.window
self.window.mainFrame.needsSave = self.dummy
self.test()
def dummy(self):
pass
def test(self):
"""Testing code goes here"""
from diffpy.pdfgui.control.parameter import Parameter
self.window.parameters = {
3: Parameter(3),
17: Parameter(17),
11: Parameter(11),
}
self.window.parameters[3].setInitial(1)
self.window.parameters[17].setInitial(0.55)
self.window.parameters[11].setInitial(5.532)
self.window.refresh()
class MyApp(wx.App):
def onInit(self):
frame_1 = MyFrame(None, -1, "")
self.SetTopWindow(frame_1)
frame_1.Show()
return 1
app = MyApp(0)
app.MainLoop()
# end of testing code #####################################################