-
-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathMatchingObjectsBrowser.cs
More file actions
463 lines (399 loc) · 13.4 KB
/
Copy pathMatchingObjectsBrowser.cs
File metadata and controls
463 lines (399 loc) · 13.4 KB
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
// Copyright (c) 2014-2017 SIL International
// This software is licensed under the LGPL, version 2.1 or later
// (http://www.gnu.org/licenses/lgpl-2.1.html)
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Xml;
using SIL.FieldWorks.Common.FwUtils;
using SIL.LCModel;
using SIL.LCModel.Application;
using SIL.Xml;
using XCore;
using SIL.FieldWorks.Filters;
using System.Collections;
using SIL.LCModel.Core.Text;
using SIL.LCModel.Core.WritingSystems;
using SIL.LCModel.Core.KernelInterfaces;
using SIL.PlatformUtilities;
namespace SIL.FieldWorks.Common.Controls
{
/// <summary>
/// A browse view that displays the results of a search.
/// </summary>
public class MatchingObjectsBrowser : UserControl
{
#region Events
/// <summary>
/// Declare an event to deal with selection changed.
/// </summary>
public event FwSelectionChangedEventHandler SelectionChanged;
/// <summary>
/// Occurs when a specific object is selected with a double-click or enter key.
/// </summary>
public event FwSelectionChangedEventHandler SelectionMade;
/// <summary>
/// Occurs when the search has completed.
/// </summary>
public event EventHandler SearchCompleted;
/// <summary>
/// Occurs when the underlying BrowseViewer's columns have changed.
/// </summary>
public event EventHandler ColumnsChanged;
#endregion Events
#region Data members
private const int ListFlid = ObjectListPublisher.MinFakeFlid + 1111;
private LcmCache m_cache;
private IVwStylesheet m_stylesheet; // used to figure font heights.
private Mediator m_mediator;
private PropertyTable m_propertyTable;
private BrowseViewer m_bvMatches;
private ObjectListPublisher m_listPublisher;
private SearchEngine m_searchEngine;
private ICmObject m_selObject;
private ICmObject m_startingObject;
private string[] m_visibleColumns;
#endregion Data members
#region Disposal methods
/// <summary>
/// Check to see if the object has been disposed.
/// All public Properties and Methods should call this
/// before doing anything else.
/// </summary>
public void CheckDisposed()
{
if (IsDisposed)
throw new ObjectDisposedException(String.Format("'{0}' in use after being disposed.", GetType().Name));
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
System.Diagnostics.Debug.WriteLineIf(!disposing, "****************** Missing Dispose() call for " + GetType().Name + ". ******************");
// Must not be run more than once.
if (IsDisposed)
return;
if (disposing)
{
m_searchEngine.SearchCompleted -= m_searchEngine_SearchCompleted;
}
m_cache = null;
base.Dispose(disposing);
}
#endregion Disposal methods
#region Properties
/// <summary>
/// Gets the selected object.
/// </summary>
/// <value>The selected object.</value>
public ICmObject SelectedObject
{
get
{
return m_selObject;
}
}
/// <summary>
/// Gets or sets the starting object.
/// </summary>
public ICmObject StartingObject
{
get
{
CheckDisposed();
return m_startingObject;
}
set
{
CheckDisposed();
m_startingObject = value;
}
}
/// <summary>
/// Delegate for FilterResult.
/// </summary>
public delegate bool FilterResultDelegate(int hvo);
/// <summary>
/// Should we filter a result?
/// </summary>
public FilterResultDelegate FilterResult;
/// <summary>
/// Used by a Find dialog's SearchEngine to determine whether to search on a particular field or not
/// </summary>
public bool IsVisibleColumn(string keyString)
{
CheckDisposed();
return m_visibleColumns.Any(columnLayoutName => columnLayoutName.Contains(keyString));
}
#endregion Properties
#region Public methods
/// <summary>
/// Initialize the control, creating the BrowseViewer among other things.
/// </summary>
/// <param name="cache">The cache.</param>
/// <param name="stylesheet">The stylesheet.</param>
/// <param name="mediator">The mediator.</param>
/// <param name="propertyTable"></param>
/// <param name="configNode">The config node.</param>
/// <param name="searchEngine">The search engine.</param>
public void Initialize(LcmCache cache, IVwStylesheet stylesheet, Mediator mediator, PropertyTable propertyTable, XmlNode configNode,
SearchEngine searchEngine)
{
Initialize(cache, stylesheet, mediator, propertyTable, configNode, searchEngine, null);
}
/// <summary>
/// Initialize the control, creating the BrowseViewer among other things.
/// </summary>
/// <param name="cache">The cache.</param>
/// <param name="stylesheet">The stylesheet.</param>
/// <param name="mediator">The mediator.</param>
/// <param name="propertyTable"></param>
/// <param name="configNode">The config node.</param>
/// <param name="searchEngine">The search engine.</param>
/// <param name="reversalWs">The reversal writing system.</param>
public void Initialize(LcmCache cache, IVwStylesheet stylesheet, Mediator mediator, PropertyTable propertyTable, XmlNode configNode,
SearchEngine searchEngine, CoreWritingSystemDefinition reversalWs)
{
CheckDisposed();
m_cache = cache;
m_stylesheet = stylesheet;
m_mediator = mediator;
m_propertyTable = propertyTable;
m_searchEngine = searchEngine;
m_searchEngine.SearchCompleted += m_searchEngine_SearchCompleted;
SuspendLayout();
CreateBrowseViewer(configNode, reversalWs);
ResumeLayout(false);
}
private void m_searchEngine_SearchCompleted(object sender, SearchCompletedEventArgs e)
{
UpdateResults(e.Fields.FirstOrDefault(), e.Results);
// On the completion of a new search set the selection to the first result without stealing the focus
// from any other controls.
if(m_bvMatches.BrowseView.IsHandleCreated) // hotfix paranoia test
{
var oldEnabledState = m_bvMatches.Enabled;
m_bvMatches.Enabled = false;
// disable the control before changing the selection so that the focus won't change
m_bvMatches.SelectedIndex = m_bvMatches.AllItems.Count > 0 ? 0 : -1;
m_bvMatches.Enabled = oldEnabledState; // restore the control to it's previous enabled state
}
}
/// <summary>
/// Searches the specified fields asynchronously.
/// </summary>
public void SearchAsync(IEnumerable<SearchField> fields)
{
// Start the search
m_searchEngine.SearchAsync(fields);
}
/// <summary>
/// respond to an up arrow key in the find select box
/// </summary>
public void SelectNext()
{
int i = m_bvMatches.SelectedIndex;
if (i != -1 && i + 1 < m_bvMatches.AllItems.Count)
m_bvMatches.SelectedIndex = i + 1;
}
/// <summary>
/// respond to a down arrow key in the find select box
/// </summary>
public void SelectPrevious()
{
int i = m_bvMatches.SelectedIndex;
if (i > 0)
m_bvMatches.SelectedIndex = i - 1;
}
#endregion
#region Event Handlers
/// <summary>
/// This comes from a single click on a row in the browse view.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void m_bvMatches_SelectionChanged(object sender, FwObjectSelectionEventArgs e)
{
m_selObject = m_cache.ServiceLocator.GetObject(e.Hvo);
FireSelectionChanged();
}
/// <summary>
/// This comes from a double click on a row in the browse view.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void m_bvMatches_SelectionMade(object sender, FwObjectSelectionEventArgs e)
{
m_selObject = m_cache.ServiceLocator.GetObject(e.Hvo);
FireSelectionMade();
}
/// <summary>
/// This comes from modifying the browse view columns
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void m_bvMatches_ColumnsChanged(object sender, EventArgs e)
{
if (e is ColumnWidthChangedEventArgs)
return; // don't want to know about this kind
UpdateVisibleColumns();
if (ColumnsChanged != null)
// Find dialogs can subscribe to this to know when to check for new search fields
ColumnsChanged(this, new EventArgs());
}
private bool m_recursionProtection = false; // FWNX-262
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Control.Enter"/> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
protected override void OnEnter(EventArgs e)
{
if (Platform.IsMono)
{
if (m_recursionProtection) // FWNX-262
return;
m_recursionProtection = true;
}
m_bvMatches.SelectedRowHighlighting = XmlBrowseViewBase.SelectionHighlighting.border;
base.OnEnter(e);
m_bvMatches.Select();
m_recursionProtection = false;
}
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Control.Leave"/> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
m_bvMatches.SelectedRowHighlighting = XmlBrowseViewBase.SelectionHighlighting.all;
}
#endregion Event Handlers
#region Other methods
private void CreateBrowseViewer(XmlNode configNode, CoreWritingSystemDefinition reversalWs)
{
m_listPublisher = new ObjectListPublisher(m_cache.DomainDataByFlid as ISilDataAccessManaged, ListFlid);
m_bvMatches = new BrowseViewer(configNode, m_cache.LanguageProject.LexDbOA.Hvo, ListFlid, m_cache, m_mediator,
m_propertyTable, null, m_listPublisher);
m_bvMatches.SuspendLayout();
m_bvMatches.Location = new Point(0, 0);
m_bvMatches.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right;
m_bvMatches.Name = "m_bvMatches";
m_bvMatches.Sorter = null;
m_bvMatches.TabStop = false;
m_bvMatches.StyleSheet = m_stylesheet;
m_bvMatches.Dock = DockStyle.Fill;
if (reversalWs != null)
m_bvMatches.BrowseView.Vc.ReversalWs = reversalWs.Handle;
m_bvMatches.SelectionChanged += m_bvMatches_SelectionChanged;
m_bvMatches.SelectionMade += m_bvMatches_SelectionMade;
UpdateVisibleColumns();
Controls.Add(m_bvMatches);
m_bvMatches.ResumeLayout();
m_bvMatches.ColumnsChanged += m_bvMatches_ColumnsChanged;
}
private void UpdateVisibleColumns()
{
var results = new List<string>();
foreach (var columnSpec in m_bvMatches.ColumnSpecs)
{
var colLabel = columnSpec.GetOptionalStringAttribute("layout", null);
if (colLabel == null)
{
// In this case we are likely dealing with a dialog that does NOT use IsVisibleColumn()
// and there will be one pre-determined SearchField
continue;
}
results.Add(colLabel);
}
m_visibleColumns = results.ToArray();
}
private void UpdateResults(SearchField firstField, IEnumerable<int> results)
{
ITsString firstSearchStr = firstField.String;
// if the firstSearchStr is null we can't get its writing system
RecordSorter sorter = null;
if (firstSearchStr != null)
{
int ws = firstSearchStr.get_WritingSystemAt(0);
sorter = CreateFindResultSorter(firstSearchStr, ws);
}
int[] hvos;
if (sorter != null)
{
// Convert each ICmObject in results to a IManyOnePathSortItem, and sort
// using the sorter.
var records = new ArrayList();
foreach (int hvo in results.Where(hvo => StartingObject == null || StartingObject.Hvo != hvo))
records.Add(new ManyOnePathSortItem(hvo, null, null));
sorter.Sort(records);
hvos = records.Cast<IManyOnePathSortItem>().Select(i => i.KeyObject).ToArray();
}
else
{
hvos = results.Where(hvo => StartingObject == null || StartingObject.Hvo != hvo).ToArray();
}
if (FilterResult != null)
hvos = hvos.Where(hvo => !FilterResult(hvo)).ToArray();
int count = hvos.Length;
int prevIndex = m_bvMatches.SelectedIndex;
int prevHvo = prevIndex == -1 ? 0 : m_bvMatches.AllItems[prevIndex];
m_listPublisher.CacheVecProp(m_cache.LanguageProject.LexDbOA.Hvo, hvos, true);
TabStop = count > 0;
// Disable the list so that it doesn't steal the focus (LT-9481)
m_bvMatches.Enabled = false;
try
{
// LT-6366
if (count == 0)
{
if (m_bvMatches.BrowseView.IsHandleCreated)
m_bvMatches.SelectedIndex = -1;
m_selObject = null;
}
else
{
int newIndex = 0;
var allItems = m_bvMatches.AllItems; // This is an important optimization; each call marshals the whole list!
for (int i = 0; i < allItems.Count; i++)
{
if (allItems[i] == prevHvo)
{
newIndex = i;
break;
}
}
if (m_bvMatches.BrowseView.IsHandleCreated)
m_bvMatches.SelectedIndex = newIndex;
m_selObject = m_cache.ServiceLocator.GetObject(allItems[newIndex]);
FireSelectionChanged();
}
}
finally
{
m_bvMatches.Enabled = true;
}
if (!m_searchEngine.IsBusy && SearchCompleted != null)
SearchCompleted(this, new EventArgs());
}
private FindResultSorter CreateFindResultSorter(ITsString firstSearchStr, int ws)
{
var browseViewSorter = m_bvMatches.CreateSorterForFirstColumn(ws);
return browseViewSorter == null ? null: new FindResultSorter(firstSearchStr, browseViewSorter);
}
private void FireSelectionChanged()
{
if (SelectionChanged != null)
SelectionChanged(this, new FwObjectSelectionEventArgs(m_selObject.Hvo));
}
private void FireSelectionMade()
{
if (SelectionMade != null)
SelectionMade(this, new FwObjectSelectionEventArgs(m_selObject.Hvo));
}
#endregion Other methods
}
}