2019-11-23 04:45:57 +00:00
|
|
|
|
/*
|
|
|
|
|
* Copyright 2019 faddenSoft
|
|
|
|
|
*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2019-11-25 22:27:38 +00:00
|
|
|
|
using System.Collections.ObjectModel;
|
2019-11-23 04:45:57 +00:00
|
|
|
|
using System.ComponentModel;
|
2019-11-25 22:27:38 +00:00
|
|
|
|
using System.Diagnostics;
|
2019-11-23 04:45:57 +00:00
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
using System.Windows;
|
2019-11-25 22:27:38 +00:00
|
|
|
|
using System.Windows.Controls;
|
|
|
|
|
using System.Windows.Input;
|
2019-11-23 04:45:57 +00:00
|
|
|
|
|
2019-11-25 22:27:38 +00:00
|
|
|
|
using Asm65;
|
|
|
|
|
using PluginCommon;
|
|
|
|
|
|
2019-11-23 04:45:57 +00:00
|
|
|
|
namespace SourceGen.WpfGui {
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Visualization set editor.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public partial class EditVisualizationSet : Window, INotifyPropertyChanged {
|
2019-12-18 00:40:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Modified visualization set. Only valid after OK is hit.
|
|
|
|
|
/// </summary>
|
2019-11-23 04:45:57 +00:00
|
|
|
|
public VisualizationSet NewVisSet { get; private set; }
|
|
|
|
|
|
2019-12-22 02:04:44 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// List of Visualization serial numbers that were removed from the set. The caller
|
|
|
|
|
/// can use this to update animations in other sets that referred to the removed items.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// We have to use serial numbers because the user might have edited the Visualization
|
|
|
|
|
/// before removing it.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public List<int> RemovedSerials { get; private set; }
|
|
|
|
|
|
2019-11-25 22:27:38 +00:00
|
|
|
|
private DisasmProject mProject;
|
|
|
|
|
private Formatter mFormatter;
|
2019-12-03 00:38:32 +00:00
|
|
|
|
private VisualizationSet mOrigSet;
|
|
|
|
|
private int mOffset;
|
2019-11-25 22:27:38 +00:00
|
|
|
|
|
2019-12-22 02:04:44 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// ItemsSource for visualizationGrid.
|
|
|
|
|
/// </summary>
|
2019-11-25 22:27:38 +00:00
|
|
|
|
public ObservableCollection<Visualization> VisualizationList { get; private set; } =
|
|
|
|
|
new ObservableCollection<Visualization>();
|
2019-11-23 04:45:57 +00:00
|
|
|
|
|
2019-12-03 00:38:32 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// True if there are plugins that implement the visualization generation interface.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool HasVisPlugins {
|
|
|
|
|
get { return mHasVisPlugins; }
|
|
|
|
|
set { mHasVisPlugins = value; OnPropertyChanged(); }
|
|
|
|
|
}
|
|
|
|
|
private bool mHasVisPlugins;
|
|
|
|
|
|
2019-12-04 23:50:19 +00:00
|
|
|
|
public Visibility ScriptWarningVisible {
|
|
|
|
|
get { return mHasVisPlugins ? Visibility.Collapsed : Visibility.Visible; }
|
|
|
|
|
// this can't change while the dialog is open, so don't need OnPropertyChanged
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-29 01:14:29 +00:00
|
|
|
|
//
|
|
|
|
|
// Every action causes a selection change, so we don't explicitly call an "update
|
|
|
|
|
// controls" function.
|
|
|
|
|
//
|
|
|
|
|
|
2019-12-03 00:38:32 +00:00
|
|
|
|
public bool IsEditEnabled {
|
|
|
|
|
get { return mIsEditEnabled; }
|
|
|
|
|
set { mIsEditEnabled = value; OnPropertyChanged(); }
|
|
|
|
|
}
|
|
|
|
|
private bool mIsEditEnabled;
|
|
|
|
|
|
|
|
|
|
public bool IsRemoveEnabled {
|
|
|
|
|
get { return mIsRemoveEnabled; }
|
|
|
|
|
set { mIsRemoveEnabled = value; OnPropertyChanged(); }
|
|
|
|
|
}
|
|
|
|
|
private bool mIsRemoveEnabled;
|
|
|
|
|
|
|
|
|
|
public bool IsUpEnabled {
|
|
|
|
|
get { return mIsUpEnabled; }
|
|
|
|
|
set { mIsUpEnabled = value; OnPropertyChanged(); }
|
|
|
|
|
}
|
|
|
|
|
private bool mIsUpEnabled;
|
|
|
|
|
|
|
|
|
|
public bool IsDownEnabled {
|
|
|
|
|
get { return mIsDownEnabled; }
|
|
|
|
|
set { mIsDownEnabled = value; OnPropertyChanged(); }
|
|
|
|
|
}
|
|
|
|
|
private bool mIsDownEnabled;
|
|
|
|
|
|
2019-11-23 04:45:57 +00:00
|
|
|
|
// INotifyPropertyChanged implementation
|
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
|
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
|
|
|
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-25 22:27:38 +00:00
|
|
|
|
public EditVisualizationSet(Window owner, DisasmProject project, Formatter formatter,
|
2019-12-03 00:38:32 +00:00
|
|
|
|
VisualizationSet curSet, int offset) {
|
2019-11-23 04:45:57 +00:00
|
|
|
|
InitializeComponent();
|
|
|
|
|
Owner = owner;
|
|
|
|
|
DataContext = this;
|
|
|
|
|
|
2019-11-25 22:27:38 +00:00
|
|
|
|
mProject = project;
|
|
|
|
|
mFormatter = formatter;
|
2019-12-03 00:38:32 +00:00
|
|
|
|
mOrigSet = curSet;
|
|
|
|
|
mOffset = offset;
|
2019-11-25 22:27:38 +00:00
|
|
|
|
|
2019-12-22 02:04:44 +00:00
|
|
|
|
RemovedSerials = new List<int>();
|
|
|
|
|
|
2019-11-23 04:45:57 +00:00
|
|
|
|
if (curSet != null) {
|
2019-12-04 23:50:19 +00:00
|
|
|
|
// Populate the data grid ItemsSource.
|
2019-11-25 22:27:38 +00:00
|
|
|
|
foreach (Visualization vis in curSet) {
|
|
|
|
|
VisualizationList.Add(vis);
|
|
|
|
|
}
|
2019-11-23 04:45:57 +00:00
|
|
|
|
}
|
2019-12-04 23:50:19 +00:00
|
|
|
|
if (VisualizationList.Count > 0) {
|
|
|
|
|
visualizationGrid.SelectedIndex = 0;
|
|
|
|
|
}
|
2019-11-23 04:45:57 +00:00
|
|
|
|
|
2019-12-03 00:38:32 +00:00
|
|
|
|
// Check to see if we have any relevant plugins. If not, disable New/Edit.
|
2019-12-28 22:00:48 +00:00
|
|
|
|
Dictionary<string, IPlugin> plugins = project.GetActivePlugins();
|
|
|
|
|
foreach (IPlugin chkPlug in plugins.Values) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
if (chkPlug is IPlugin_Visualizer) {
|
|
|
|
|
HasVisPlugins = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-23 04:45:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OkButton_Click(object sender, RoutedEventArgs e) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
NewVisSet = MakeVisSet();
|
|
|
|
|
DialogResult = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Window_Closing(object sender, CancelEventArgs e) {
|
|
|
|
|
if (DialogResult == true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check to see if changes have been made.
|
2020-01-01 04:39:51 +00:00
|
|
|
|
VisualizationSet newSet;
|
|
|
|
|
if (VisualizationList.Count == 0) {
|
|
|
|
|
newSet = null;
|
|
|
|
|
} else {
|
|
|
|
|
newSet = MakeVisSet();
|
|
|
|
|
}
|
2019-12-03 00:38:32 +00:00
|
|
|
|
if (newSet != mOrigSet) {
|
|
|
|
|
string msg = (string)FindResource("str_ConfirmDiscardChanges");
|
|
|
|
|
string caption = (string)FindResource("str_ConfirmDiscardChangesCaption");
|
|
|
|
|
MessageBoxResult result = MessageBox.Show(msg, caption, MessageBoxButton.OKCancel,
|
|
|
|
|
MessageBoxImage.Question);
|
|
|
|
|
if (result == MessageBoxResult.Cancel) {
|
|
|
|
|
e.Cancel = true;
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
2019-11-23 04:45:57 +00:00
|
|
|
|
}
|
2019-12-03 00:38:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-25 22:27:38 +00:00
|
|
|
|
private void VisualizationList_SelectionChanged(object sender,
|
|
|
|
|
SelectionChangedEventArgs e) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
bool isItemSelected = (visualizationGrid.SelectedItem != null);
|
|
|
|
|
IsEditEnabled = HasVisPlugins && isItemSelected;
|
|
|
|
|
IsRemoveEnabled = isItemSelected;
|
|
|
|
|
IsUpEnabled = isItemSelected && visualizationGrid.SelectedIndex != 0;
|
|
|
|
|
IsDownEnabled = isItemSelected &&
|
|
|
|
|
visualizationGrid.SelectedIndex != VisualizationList.Count - 1;
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void VisualizationList_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
EditSelectedItem();
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 02:18:31 +00:00
|
|
|
|
private void NewVisualizationButton_Click(object sender, RoutedEventArgs e) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
|
2019-12-18 00:40:27 +00:00
|
|
|
|
CreateEditedSetList(), null);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
if (dlg.ShowDialog() != true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
VisualizationList.Add(dlg.NewVis);
|
2019-12-04 23:50:19 +00:00
|
|
|
|
visualizationGrid.SelectedIndex = VisualizationList.Count - 1;
|
2020-01-01 04:39:51 +00:00
|
|
|
|
|
|
|
|
|
okButton.Focus();
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 02:18:31 +00:00
|
|
|
|
private void NewWireframeAnimationButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
// TODO(xyzzy)
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-18 00:40:27 +00:00
|
|
|
|
private void NewBitmapAnimationButton_Click(object sender, RoutedEventArgs e) {
|
2019-12-20 23:05:16 +00:00
|
|
|
|
EditBitmapAnimation dlg = new EditBitmapAnimation(this, mOffset,
|
|
|
|
|
CreateEditedSetList(), null);
|
2019-12-18 00:40:27 +00:00
|
|
|
|
if (dlg.ShowDialog() != true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-12-20 23:05:16 +00:00
|
|
|
|
VisualizationList.Add(dlg.NewAnim);
|
|
|
|
|
visualizationGrid.SelectedIndex = VisualizationList.Count - 1;
|
2020-01-01 04:39:51 +00:00
|
|
|
|
|
|
|
|
|
okButton.Focus();
|
2019-12-18 00:40:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-25 22:27:38 +00:00
|
|
|
|
private void EditButton_Click(object sender, RoutedEventArgs e) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
EditSelectedItem();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EditSelectedItem() {
|
|
|
|
|
if (!IsEditEnabled) {
|
2019-12-18 00:40:27 +00:00
|
|
|
|
// can get called here by a double-click
|
2019-12-03 00:38:32 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Visualization item = (Visualization)visualizationGrid.SelectedItem;
|
2019-12-20 23:05:16 +00:00
|
|
|
|
Visualization newVis;
|
2019-12-03 00:38:32 +00:00
|
|
|
|
|
2019-12-20 23:05:16 +00:00
|
|
|
|
if (item is VisualizationAnimation) {
|
|
|
|
|
EditBitmapAnimation dlg = new EditBitmapAnimation(this, mOffset,
|
|
|
|
|
CreateEditedSetList(), (VisualizationAnimation)item);
|
|
|
|
|
if (dlg.ShowDialog() != true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
newVis = dlg.NewAnim;
|
|
|
|
|
} else {
|
|
|
|
|
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
|
|
|
|
|
CreateEditedSetList(), item);
|
|
|
|
|
if (dlg.ShowDialog() != true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
newVis = dlg.NewVis;
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
2019-11-27 02:54:42 +00:00
|
|
|
|
|
2019-12-03 00:38:32 +00:00
|
|
|
|
int index = VisualizationList.IndexOf(item);
|
|
|
|
|
VisualizationList.Remove(item);
|
2019-12-20 23:05:16 +00:00
|
|
|
|
VisualizationList.Insert(index, newVis);
|
2019-12-04 23:50:19 +00:00
|
|
|
|
visualizationGrid.SelectedIndex = index;
|
2019-12-07 01:19:27 +00:00
|
|
|
|
|
|
|
|
|
okButton.Focus();
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RemoveButton_Click(object sender, RoutedEventArgs e) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
Visualization item = (Visualization)visualizationGrid.SelectedItem;
|
2019-12-29 01:14:29 +00:00
|
|
|
|
int index = visualizationGrid.SelectedIndex;
|
|
|
|
|
VisualizationList.RemoveAt(index);
|
|
|
|
|
|
|
|
|
|
// Keep selection at same index, unless we just removed the item at the end.
|
2019-12-04 23:50:19 +00:00
|
|
|
|
if (index == VisualizationList.Count) {
|
|
|
|
|
index--;
|
|
|
|
|
}
|
|
|
|
|
if (index >= 0) {
|
|
|
|
|
visualizationGrid.SelectedIndex = index;
|
|
|
|
|
}
|
2019-12-22 02:04:44 +00:00
|
|
|
|
|
|
|
|
|
RemovedSerials.Add(item.SerialNumber);
|
|
|
|
|
|
|
|
|
|
// Update any animations in this set. Animations in other sets will be updated later.
|
|
|
|
|
// (This is a bit awkward because we can't modify VisualizationList while iterating
|
|
|
|
|
// through it, and there's no simple "replace entry" operation on an observable
|
|
|
|
|
// collection. Fortunately we don't do this often and the data sets are small.)
|
|
|
|
|
List<VisualizationAnimation> needsUpdate = new List<VisualizationAnimation>();
|
|
|
|
|
foreach (Visualization vis in VisualizationList) {
|
|
|
|
|
if (vis is VisualizationAnimation) {
|
|
|
|
|
VisualizationAnimation visAnim = (VisualizationAnimation)vis;
|
|
|
|
|
if (visAnim.ContainsSerial(item.SerialNumber)) {
|
|
|
|
|
needsUpdate.Add(visAnim);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
foreach (VisualizationAnimation visAnim in needsUpdate) {
|
|
|
|
|
VisualizationAnimation newAnim;
|
|
|
|
|
if (VisualizationAnimation.StripEntries(visAnim,
|
|
|
|
|
new List<int>(1) { item.SerialNumber }, out newAnim)) {
|
2019-12-23 00:56:57 +00:00
|
|
|
|
if (newAnim.Count == 0) {
|
2019-12-22 02:04:44 +00:00
|
|
|
|
VisualizationList.Remove(visAnim);
|
|
|
|
|
} else {
|
2019-12-25 01:53:04 +00:00
|
|
|
|
newAnim.GenerateImage(CreateEditedSetList());
|
2019-12-22 02:04:44 +00:00
|
|
|
|
index = VisualizationList.IndexOf(visAnim);
|
|
|
|
|
VisualizationList.Remove(visAnim);
|
|
|
|
|
VisualizationList.Insert(index, newAnim);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpButton_Click(object sender, RoutedEventArgs e) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
Visualization item = (Visualization)visualizationGrid.SelectedItem;
|
2019-12-29 01:14:29 +00:00
|
|
|
|
int index = visualizationGrid.SelectedIndex;
|
2019-12-03 00:38:32 +00:00
|
|
|
|
Debug.Assert(index > 0);
|
2019-12-29 01:14:29 +00:00
|
|
|
|
VisualizationList.RemoveAt(index);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
VisualizationList.Insert(index - 1, item);
|
|
|
|
|
visualizationGrid.SelectedIndex = index - 1;
|
2020-01-01 17:08:22 +00:00
|
|
|
|
visualizationGrid.ScrollIntoView(item);
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DownButton_Click(object sender, RoutedEventArgs e) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
Visualization item = (Visualization)visualizationGrid.SelectedItem;
|
2019-12-29 01:14:29 +00:00
|
|
|
|
int index = visualizationGrid.SelectedIndex;
|
2019-12-20 23:05:16 +00:00
|
|
|
|
Debug.Assert(index >= 0 && index < VisualizationList.Count - 1);
|
2019-12-29 01:14:29 +00:00
|
|
|
|
VisualizationList.RemoveAt(index);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
VisualizationList.Insert(index + 1, item);
|
|
|
|
|
visualizationGrid.SelectedIndex = index + 1;
|
2020-01-01 17:08:22 +00:00
|
|
|
|
visualizationGrid.ScrollIntoView(item);
|
2019-11-25 22:27:38 +00:00
|
|
|
|
}
|
2019-12-18 00:40:27 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a VisualizationSet from the current list of Visualizations.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>New VisualizationSet.</returns>
|
|
|
|
|
private VisualizationSet MakeVisSet() {
|
|
|
|
|
VisualizationSet newSet = new VisualizationSet(VisualizationList.Count);
|
|
|
|
|
foreach (Visualization vis in VisualizationList) {
|
|
|
|
|
newSet.Add(vis);
|
|
|
|
|
}
|
|
|
|
|
return newSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generates a list of VisualizationSet references. This is the list from the
|
|
|
|
|
/// DisasmProject, but with the set we're editing added or substituted.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// The editors sometimes need access to the full collection of Visualization objects,
|
|
|
|
|
/// such as when testing a tag for uniqueness or getting a list of all bitmap
|
|
|
|
|
/// frames for an animation. The editor needs access to recent edits that have not
|
|
|
|
|
/// been pushed to the project yet.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <returns>List of VisualizationSet.</returns>
|
|
|
|
|
private SortedList<int, VisualizationSet> CreateEditedSetList() {
|
|
|
|
|
SortedList<int, VisualizationSet> mixList =
|
|
|
|
|
new SortedList<int, VisualizationSet>(mProject.VisualizationSets.Count);
|
|
|
|
|
|
|
|
|
|
mixList[mOffset] = MakeVisSet();
|
|
|
|
|
foreach (KeyValuePair<int, VisualizationSet> kvp in mProject.VisualizationSets) {
|
|
|
|
|
// Skip the entry for mOffset (if it exists).
|
|
|
|
|
if (kvp.Key != mOffset) {
|
|
|
|
|
mixList[kvp.Key] = kvp.Value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return mixList;
|
|
|
|
|
}
|
2019-12-20 23:05:16 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds a Visualization with a matching tag, searching across all sets in the
|
|
|
|
|
/// edited list.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="tag">Tag to search for.</param>
|
|
|
|
|
/// <returns>Matching Visualization, or null if not found.</returns>
|
|
|
|
|
public static Visualization FindVisualizationByTag(SortedList<int, VisualizationSet> list,
|
|
|
|
|
string tag) {
|
|
|
|
|
foreach (KeyValuePair<int, VisualizationSet> kvp in list) {
|
|
|
|
|
foreach (Visualization vis in kvp.Value) {
|
|
|
|
|
if (vis.Tag == tag) {
|
|
|
|
|
return vis;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2019-11-23 04:45:57 +00:00
|
|
|
|
}
|
|
|
|
|
}
|