From a6978e2bfef7ba03a27bd26e378a894348deb533 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Thu, 27 Feb 2025 13:42:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=BF=9B=E5=BA=A6=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/BatchOpenSpineDialog.Designer.cs | 29 ++-- SpineViewer/src/MainForm.Designer.cs | 38 ++--- SpineViewer/src/ProgressDialog.Designer.cs | 137 ++++++++++++++++++ SpineViewer/src/ProgressDialog.cs | 60 ++++++++ SpineViewer/src/ProgressDialog.resx | 123 ++++++++++++++++ SpineViewer/src/Spine/Spine.cs | 20 +-- SpineViewer/src/SpineListView.cs | 54 +++++-- 7 files changed, 403 insertions(+), 58 deletions(-) create mode 100644 SpineViewer/src/ProgressDialog.Designer.cs create mode 100644 SpineViewer/src/ProgressDialog.cs create mode 100644 SpineViewer/src/ProgressDialog.resx diff --git a/SpineViewer/src/BatchOpenSpineDialog.Designer.cs b/SpineViewer/src/BatchOpenSpineDialog.Designer.cs index 540aacb..e0acd35 100644 --- a/SpineViewer/src/BatchOpenSpineDialog.Designer.cs +++ b/SpineViewer/src/BatchOpenSpineDialog.Designer.cs @@ -30,6 +30,7 @@ { panel = new Panel(); tableLayoutPanel1 = new TableLayoutPanel(); + label4 = new Label(); label3 = new Label(); comboBox_Version = new ComboBox(); tableLayoutPanel2 = new TableLayoutPanel(); @@ -39,7 +40,6 @@ button_SelectSkel = new Button(); label_Tip = new Label(); openFileDialog_Skel = new OpenFileDialog(); - label4 = new Label(); panel.SuspendLayout(); tableLayoutPanel1.SuspendLayout(); tableLayoutPanel2.SuspendLayout(); @@ -79,6 +79,19 @@ tableLayoutPanel1.Size = new Size(1026, 424); tableLayoutPanel1.TabIndex = 1; // + // label4 + // + label4.AutoSize = true; + tableLayoutPanel1.SetColumnSpan(label4, 4); + label4.Dock = DockStyle.Fill; + label4.Location = new Point(15, 15); + label4.Margin = new Padding(15); + label4.Name = "label4"; + label4.Size = new Size(996, 24); + label4.TabIndex = 14; + label4.Text = "说明:批量导入只需要选择skel文件,atlas文件需要在同目录下并且与skel文件名相同"; + label4.TextAlign = ContentAlignment.MiddleCenter; + // // label3 // label3.Anchor = AnchorStyles.Right; @@ -147,6 +160,7 @@ tableLayoutPanel1.SetColumnSpan(listBox_FilePath, 2); listBox_FilePath.Dock = DockStyle.Fill; listBox_FilePath.FormattingEnabled = true; + listBox_FilePath.HorizontalScrollbar = true; listBox_FilePath.ItemHeight = 24; listBox_FilePath.Location = new Point(3, 97); listBox_FilePath.Name = "listBox_FilePath"; @@ -182,19 +196,6 @@ openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json"; openFileDialog_Skel.Multiselect = true; // - // label4 - // - label4.AutoSize = true; - tableLayoutPanel1.SetColumnSpan(label4, 4); - label4.Dock = DockStyle.Fill; - label4.Location = new Point(15, 15); - label4.Margin = new Padding(15); - label4.Name = "label4"; - label4.Size = new Size(996, 24); - label4.TabIndex = 14; - label4.Text = "说明:批量导入只需要选择skel文件,atlas文件需要在同目录下并且与skel文件名相同"; - label4.TextAlign = ContentAlignment.MiddleCenter; - // // BatchOpenSpineDialog // AcceptButton = button_Ok; diff --git a/SpineViewer/src/MainForm.Designer.cs b/SpineViewer/src/MainForm.Designer.cs index 7a669b0..b610cb3 100644 --- a/SpineViewer/src/MainForm.Designer.cs +++ b/SpineViewer/src/MainForm.Designer.cs @@ -87,7 +87,7 @@ menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Help }); menuStrip.Location = new Point(0, 0); menuStrip.Name = "menuStrip"; - menuStrip.Size = new Size(1519, 32); + menuStrip.Size = new Size(1741, 32); menuStrip.TabIndex = 0; menuStrip.Text = "菜单"; // @@ -162,7 +162,7 @@ rtbLog.Margin = new Padding(3, 2, 3, 2); rtbLog.Name = "rtbLog"; rtbLog.ReadOnly = true; - rtbLog.Size = new Size(1499, 102); + rtbLog.Size = new Size(1721, 159); rtbLog.TabIndex = 0; rtbLog.Text = ""; rtbLog.WordWrap = false; @@ -184,8 +184,8 @@ // splitContainer_MainForm.Panel2.Controls.Add(rtbLog); splitContainer_MainForm.Panel2.Cursor = Cursors.Default; - splitContainer_MainForm.Size = new Size(1499, 838); - splitContainer_MainForm.SplitterDistance = 732; + splitContainer_MainForm.Size = new Size(1721, 958); + splitContainer_MainForm.SplitterDistance = 795; splitContainer_MainForm.TabIndex = 3; splitContainer_MainForm.TabStop = false; splitContainer_MainForm.SplitterMoved += splitContainer_SplitterMoved; @@ -207,8 +207,8 @@ // splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview); splitContainer_Functional.Panel2.Cursor = Cursors.Default; - splitContainer_Functional.Size = new Size(1499, 732); - splitContainer_Functional.SplitterDistance = 698; + splitContainer_Functional.Size = new Size(1721, 795); + splitContainer_Functional.SplitterDistance = 725; splitContainer_Functional.TabIndex = 2; splitContainer_Functional.TabStop = false; splitContainer_Functional.SplitterMoved += splitContainer_SplitterMoved; @@ -230,8 +230,8 @@ // splitContainer_Information.Panel2.Controls.Add(splitContainer_Config); splitContainer_Information.Panel2.Cursor = Cursors.Default; - splitContainer_Information.Size = new Size(698, 732); - splitContainer_Information.SplitterDistance = 336; + splitContainer_Information.Size = new Size(725, 795); + splitContainer_Information.SplitterDistance = 346; splitContainer_Information.TabIndex = 1; splitContainer_Information.TabStop = false; splitContainer_Information.SplitterMoved += splitContainer_SplitterMoved; @@ -243,7 +243,7 @@ groupBox_SkelList.Dock = DockStyle.Fill; groupBox_SkelList.Location = new Point(0, 0); groupBox_SkelList.Name = "groupBox_SkelList"; - groupBox_SkelList.Size = new Size(336, 732); + groupBox_SkelList.Size = new Size(346, 795); groupBox_SkelList.TabIndex = 0; groupBox_SkelList.TabStop = false; groupBox_SkelList.Text = "模型列表"; @@ -254,7 +254,7 @@ spineListView.Location = new Point(3, 26); spineListView.Name = "spineListView"; spineListView.PropertyGrid = propertyGrid_Skel; - spineListView.Size = new Size(330, 703); + spineListView.Size = new Size(340, 766); spineListView.TabIndex = 0; // // propertyGrid_Skel @@ -263,7 +263,7 @@ propertyGrid_Skel.HelpVisible = false; propertyGrid_Skel.Location = new Point(3, 26); propertyGrid_Skel.Name = "propertyGrid_Skel"; - propertyGrid_Skel.Size = new Size(352, 464); + propertyGrid_Skel.Size = new Size(369, 506); propertyGrid_Skel.TabIndex = 0; propertyGrid_Skel.ToolbarVisible = false; // @@ -284,8 +284,8 @@ // splitContainer_Config.Panel2.Controls.Add(groupBox_PreviewConfig); splitContainer_Config.Panel2.Cursor = Cursors.Default; - splitContainer_Config.Size = new Size(358, 732); - splitContainer_Config.SplitterDistance = 493; + splitContainer_Config.Size = new Size(375, 795); + splitContainer_Config.SplitterDistance = 535; splitContainer_Config.TabIndex = 0; splitContainer_Config.TabStop = false; splitContainer_Config.SplitterMoved += splitContainer_SplitterMoved; @@ -297,7 +297,7 @@ groupBox_SkelConfig.Dock = DockStyle.Fill; groupBox_SkelConfig.Location = new Point(0, 0); groupBox_SkelConfig.Name = "groupBox_SkelConfig"; - groupBox_SkelConfig.Size = new Size(358, 493); + groupBox_SkelConfig.Size = new Size(375, 535); groupBox_SkelConfig.TabIndex = 0; groupBox_SkelConfig.TabStop = false; groupBox_SkelConfig.Text = "模型参数"; @@ -307,7 +307,7 @@ groupBox_PreviewConfig.Dock = DockStyle.Fill; groupBox_PreviewConfig.Location = new Point(0, 0); groupBox_PreviewConfig.Name = "groupBox_PreviewConfig"; - groupBox_PreviewConfig.Size = new Size(358, 235); + groupBox_PreviewConfig.Size = new Size(375, 256); groupBox_PreviewConfig.TabIndex = 1; groupBox_PreviewConfig.TabStop = false; groupBox_PreviewConfig.Text = "画面参数"; @@ -318,7 +318,7 @@ groupBox_Preview.Dock = DockStyle.Fill; groupBox_Preview.Location = new Point(0, 0); groupBox_Preview.Name = "groupBox_Preview"; - groupBox_Preview.Size = new Size(797, 732); + groupBox_Preview.Size = new Size(992, 795); groupBox_Preview.TabIndex = 1; groupBox_Preview.TabStop = false; groupBox_Preview.Text = "预览画面"; @@ -330,7 +330,7 @@ panel_PreviewContainer.Location = new Point(3, 26); panel_PreviewContainer.Margin = new Padding(0); panel_PreviewContainer.Name = "panel_PreviewContainer"; - panel_PreviewContainer.Size = new Size(791, 703); + panel_PreviewContainer.Size = new Size(986, 766); panel_PreviewContainer.TabIndex = 1; // // panel_Preview @@ -348,7 +348,7 @@ panel_MainForm.Location = new Point(0, 32); panel_MainForm.Name = "panel_MainForm"; panel_MainForm.Padding = new Padding(10, 5, 10, 10); - panel_MainForm.Size = new Size(1519, 853); + panel_MainForm.Size = new Size(1741, 973); panel_MainForm.TabIndex = 4; // // openFileDialog_Skel @@ -367,7 +367,7 @@ // AutoScaleDimensions = new SizeF(11F, 24F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1519, 885); + ClientSize = new Size(1741, 1005); Controls.Add(panel_MainForm); Controls.Add(menuStrip); MainMenuStrip = menuStrip; diff --git a/SpineViewer/src/ProgressDialog.Designer.cs b/SpineViewer/src/ProgressDialog.Designer.cs new file mode 100644 index 0000000..c08576c --- /dev/null +++ b/SpineViewer/src/ProgressDialog.Designer.cs @@ -0,0 +1,137 @@ +namespace SpineViewer +{ + partial class ProgressDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + progressBar = new ProgressBar(); + panel1 = new Panel(); + tableLayoutPanel1 = new TableLayoutPanel(); + button_Cancel = new Button(); + label_Tip = new Label(); + backgroundWorker = new System.ComponentModel.BackgroundWorker(); + panel1.SuspendLayout(); + tableLayoutPanel1.SuspendLayout(); + SuspendLayout(); + // + // progressBar + // + progressBar.Dock = DockStyle.Fill; + progressBar.Location = new Point(3, 57); + progressBar.Name = "progressBar"; + progressBar.Size = new Size(552, 34); + progressBar.Style = ProgressBarStyle.Continuous; + progressBar.TabIndex = 0; + // + // panel1 + // + panel1.Controls.Add(tableLayoutPanel1); + panel1.Dock = DockStyle.Fill; + panel1.Location = new Point(0, 0); + panel1.Name = "panel1"; + panel1.Padding = new Padding(30); + panel1.Size = new Size(618, 206); + panel1.TabIndex = 1; + // + // tableLayoutPanel1 + // + tableLayoutPanel1.ColumnCount = 1; + tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + tableLayoutPanel1.Controls.Add(progressBar, 0, 1); + tableLayoutPanel1.Controls.Add(button_Cancel, 0, 2); + tableLayoutPanel1.Controls.Add(label_Tip, 0, 0); + tableLayoutPanel1.Dock = DockStyle.Fill; + tableLayoutPanel1.Location = new Point(30, 30); + tableLayoutPanel1.Name = "tableLayoutPanel1"; + tableLayoutPanel1.RowCount = 3; + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanel1.RowStyles.Add(new RowStyle()); + tableLayoutPanel1.RowStyles.Add(new RowStyle()); + tableLayoutPanel1.Size = new Size(558, 146); + tableLayoutPanel1.TabIndex = 1; + // + // button_Cancel + // + button_Cancel.Anchor = AnchorStyles.Bottom; + button_Cancel.Location = new Point(223, 109); + button_Cancel.Margin = new Padding(3, 15, 3, 3); + button_Cancel.Name = "button_Cancel"; + button_Cancel.Size = new Size(112, 34); + button_Cancel.TabIndex = 9; + button_Cancel.Text = "取消"; + button_Cancel.UseVisualStyleBackColor = true; + button_Cancel.Click += button_Cancel_Click; + // + // label_Tip + // + label_Tip.AutoSize = true; + label_Tip.Dock = DockStyle.Fill; + label_Tip.Location = new Point(3, 10); + label_Tip.Margin = new Padding(3, 10, 3, 10); + label_Tip.Name = "label_Tip"; + label_Tip.Size = new Size(552, 34); + label_Tip.TabIndex = 10; + label_Tip.Text = "正在处理 34/100"; + // + // backgroundWorker + // + backgroundWorker.WorkerReportsProgress = true; + backgroundWorker.WorkerSupportsCancellation = true; + backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged; + backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted; + // + // ProgressDialog + // + AutoScaleDimensions = new SizeF(11F, 24F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(618, 206); + ControlBox = false; + Controls.Add(panel1); + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + Name = "ProgressDialog"; + ShowIcon = false; + ShowInTaskbar = false; + StartPosition = FormStartPosition.CenterScreen; + Text = "进度"; + panel1.ResumeLayout(false); + tableLayoutPanel1.ResumeLayout(false); + tableLayoutPanel1.PerformLayout(); + ResumeLayout(false); + } + + #endregion + + private ProgressBar progressBar; + private Panel panel1; + private TableLayoutPanel tableLayoutPanel1; + private System.ComponentModel.BackgroundWorker backgroundWorker; + private Label label_Tip; + private Button button_Cancel; + } +} \ No newline at end of file diff --git a/SpineViewer/src/ProgressDialog.cs b/SpineViewer/src/ProgressDialog.cs new file mode 100644 index 0000000..ab925bd --- /dev/null +++ b/SpineViewer/src/ProgressDialog.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SpineViewer +{ + public partial class ProgressDialog : Form + { + [Category("自定义"), Description("BackgroundWorker 的 DoWork 事件")] + public event DoWorkEventHandler? Dowork + { + add { backgroundWorker.DoWork += value; } + remove { backgroundWorker.DoWork -= value; } + } + + public void RunWorkerAsync() { backgroundWorker.RunWorkerAsync(); } + public void RunWorkerAsync(object? argument) { backgroundWorker.RunWorkerAsync(argument); } + + public ProgressDialog() + { + InitializeComponent(); + } + + private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) + { + label_Tip.Text = e.UserState as string; + progressBar.Value = e.ProgressPercentage; + } + + private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (e.Error != null) + { + Program.Logger.Error(e.Error.ToString()); + MessageBox.Show(e.Error.Message, "执行出错", MessageBoxButtons.OK, MessageBoxIcon.Error); + DialogResult = DialogResult.Abort; + } + else if (e.Cancelled) + { + DialogResult = DialogResult.Cancel; + } + else + { + DialogResult= DialogResult.OK; + } + } + + private void button_Cancel_Click(object sender, EventArgs e) + { + backgroundWorker.CancelAsync(); + button_Cancel.Enabled = false; + } + } +} diff --git a/SpineViewer/src/ProgressDialog.resx b/SpineViewer/src/ProgressDialog.resx new file mode 100644 index 0000000..60ff075 --- /dev/null +++ b/SpineViewer/src/ProgressDialog.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/SpineViewer/src/Spine/Spine.cs b/SpineViewer/src/Spine/Spine.cs index ca9d1d6..4d87178 100644 --- a/SpineViewer/src/Spine/Spine.cs +++ b/SpineViewer/src/Spine/Spine.cs @@ -136,53 +136,53 @@ namespace SpineViewer.Spine /// 获取所属版本 /// [TypeConverter(typeof(VersionTypeConverter))] - [Browsable(true), Category("基本信息"), DisplayName("版本")] + [Category("基本信息"), DisplayName("版本")] public Version Version { get; } /// /// skel 文件完整路径 /// - [Browsable(true), Category("基本信息"), DisplayName("skel文件路径")] + [Category("基本信息"), DisplayName("skel文件路径")] public string SkelPath { get; } /// /// atlas 文件完整路径 /// - [Browsable(true), Category("基本信息"), DisplayName("atlas文件路径")] + [Category("基本信息"), DisplayName("atlas文件路径")] public string AtlasPath { get; } - [Browsable(true), Category("基本信息"), DisplayName("名称")] + [Category("基本信息"), DisplayName("名称")] public string Name { get; } /// /// 缩放比例 /// - [Browsable(true), Category("空间变换"), DisplayName("缩放比例")] + [Category("空间变换"), DisplayName("缩放比例")] public abstract float Scale { get; set; } /// /// 位置 /// [TypeConverter(typeof(PointFTypeConverter))] - [Browsable(true), Category("空间变换"), DisplayName("位置")] + [Category("空间变换"), DisplayName("位置")] public abstract PointF Position { get; set; } /// /// 水平翻转 /// - [Browsable(true), Category("空间变换"), DisplayName("水平翻转")] + [Category("空间变换"), DisplayName("水平翻转")] public abstract bool FlipX { get; set; } /// /// 垂直翻转 /// - [Browsable(true), Category("空间变换"), DisplayName("垂直翻转")] + [Category("空间变换"), DisplayName("垂直翻转")] public abstract bool FlipY { get; set; } /// /// 是否使用预乘Alpha /// - [Browsable(true), Category("其他"), DisplayName("预乘Alpha通道")] + [Category("其他"), DisplayName("预乘Alpha通道")] public bool UsePremultipliedAlpha { get; set; } /// @@ -202,7 +202,7 @@ namespace SpineViewer.Spine /// 当前动画名称 /// [TypeConverter(typeof(AnimationTypeConverter))] - [Browsable(true), Category("其他"), DisplayName("当前播放动画"), PropertyTab()] + [Category("其他"), DisplayName("当前播放动画"), PropertyTab()] public abstract string CurrentAnimation { get; set; } /// diff --git a/SpineViewer/src/SpineListView.cs b/SpineViewer/src/SpineListView.cs index 10caeeb..253ace0 100644 --- a/SpineViewer/src/SpineListView.cs +++ b/SpineViewer/src/SpineListView.cs @@ -16,7 +16,7 @@ namespace SpineViewer { public partial class SpineListView : UserControl { - [Browsable(true), Category("自定义"), Description("用于显示骨骼属性的属性页")] + [Category("自定义"), Description("用于显示骨骼属性的属性页")] public PropertyGrid? PropertyGrid { get; set; } /// @@ -71,40 +71,64 @@ namespace SpineViewer /// public void BatchAdd() { - var dialog = new BatchOpenSpineDialog(); - if (dialog.ShowDialog() != DialogResult.OK) + var openDialog = new BatchOpenSpineDialog(); + if (openDialog.ShowDialog() != DialogResult.OK) return; - int totalCount = dialog.SkelPaths.Length; - int errorCount = 0; + var progressDialog = new ProgressDialog(); + progressDialog.Dowork += BatchAdd_Work; + progressDialog.RunWorkerAsync(new { openDialog.SkelPaths, openDialog.Version }); + progressDialog.ShowDialog(); + } - var version = dialog.Version; - for (int i = 0; i < totalCount; i++) + private void BatchAdd_Work(object? sender, DoWorkEventArgs e) + { + var worker = sender as BackgroundWorker; + var arguments = e.Argument as dynamic; + var skelPaths = arguments.SkelPaths as string[]; + var version = (Spine.Version)arguments.Version; + + int totalCount = skelPaths.Length; + int success = 0; + int error = 0; + + for (int i = 0; i < totalCount; i++) { - var skelPath = dialog.SkelPaths[i]; - Program.Logger.Info("[{}/{}] loading {}", i + 1, totalCount, skelPath); + if (worker.CancellationPending) + break; + + var skelPath = skelPaths[i]; + + worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"正在处理 {i + 1}/{totalCount}"); try { var spine = Spine.Spine.New(version, skelPath); spines.Add(spine); - listView.Items.Add(new ListViewItem([spine.Name, spine.Version.String()], -1) { ToolTipText = spine.SkelPath }); + if (listView.InvokeRequired) + { + listView.Invoke(() => listView.Items.Add(new ListViewItem([spine.Name, spine.Version.String()], -1) { ToolTipText = spine.SkelPath })); + } + else + { + listView.Items.Add(new ListViewItem([spine.Name, spine.Version.String()], -1) { ToolTipText = spine.SkelPath }); + } + success++; } catch (Exception ex) { Program.Logger.Error(ex.ToString()); Program.Logger.Error("Failed to load {}", skelPath); - errorCount++; + error++; } } - if (errorCount > 0) + if (error > 0) { - Program.Logger.Warn("Batch load {} successfully, {} failed", totalCount - errorCount, errorCount); - MessageBox.Show($"{totalCount - errorCount}成功,{errorCount}失败", "部分骨骼加载失败", MessageBoxButtons.OK, MessageBoxIcon.Warning); + Program.Logger.Warn("Batch load {} successfully, {} failed", success, error); } else { - Program.Logger.Info("{} skel loaded successfully", totalCount); + Program.Logger.Info("{} skel loaded successfully", success); } }