diff --git a/.gitattributes b/.gitattributes index 1ff0c42..a34137a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,7 +2,6 @@ # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto - ############################################################################### # Set default behavior for command prompt diff. # @@ -11,7 +10,6 @@ # Note: This is only used by command line ############################################################################### #*.cs diff=csharp - ############################################################################### # Set the merge driver for project and solution files # @@ -34,7 +32,6 @@ #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary - ############################################################################### # behavior for image files # @@ -43,7 +40,6 @@ #*.jpg binary #*.png binary #*.gif binary - ############################################################################### # diff behavior for common document formats # @@ -61,3 +57,4 @@ #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain +AudioVisualizer/bin/Release/AudioWallpaper.1.2.1.nupkg filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 9491a2f..241c4b4 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,6 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +**/*.nupkg \ No newline at end of file diff --git a/.vs/AudioVisualizer/DesignTimeBuild/.dtbcache.v2 b/.vs/AudioVisualizer/DesignTimeBuild/.dtbcache.v2 index a7ac8fa..a0fdac2 100644 Binary files a/.vs/AudioVisualizer/DesignTimeBuild/.dtbcache.v2 and b/.vs/AudioVisualizer/DesignTimeBuild/.dtbcache.v2 differ diff --git a/.vs/AudioVisualizer/v17/.futdcache.v2 b/.vs/AudioVisualizer/v17/.futdcache.v2 index ecfc365..b14551f 100644 Binary files a/.vs/AudioVisualizer/v17/.futdcache.v2 and b/.vs/AudioVisualizer/v17/.futdcache.v2 differ diff --git a/.vs/AudioVisualizer/v17/.suo b/.vs/AudioVisualizer/v17/.suo index 5ff850d..504b7ad 100644 Binary files a/.vs/AudioVisualizer/v17/.suo and b/.vs/AudioVisualizer/v17/.suo differ diff --git a/AccompanyingAssistant/AccompanyingAssistant.csproj b/AccompanyingAssistant/AccompanyingAssistant.csproj new file mode 100644 index 0000000..c379769 --- /dev/null +++ b/AccompanyingAssistant/AccompanyingAssistant.csproj @@ -0,0 +1,188 @@ + + + + WinExe + net8.0-windows + enable + true + enable + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + \ No newline at end of file diff --git a/AccompanyingAssistant/BubbleForm.cs b/AccompanyingAssistant/BubbleForm.cs new file mode 100644 index 0000000..c2ac445 --- /dev/null +++ b/AccompanyingAssistant/BubbleForm.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace AccompanyingAssistant { + public class BubbleForm : Form { + private string _message = ""; + private int cornerRadius = 12; + private int tailHeight = 10; + private Color bubbleColor = Color.FromArgb(255, 255, 230); + private Color borderColor = Color.FromArgb(220, 220, 200); + private Font messageFont = new Font("微软雅黑", 9); + + public string Message { + get { return _message; } + set { + _message = value; + CalculateSize(); + Invalidate(); + } + } + + public BubbleForm() { + this.FormBorderStyle = FormBorderStyle.None; + this.ShowInTaskbar = false; + this.StartPosition = FormStartPosition.Manual; + this.Padding = new Padding(12, 12, 12, 12 + tailHeight); + this.BackColor = Color.Magenta; + this.TransparencyKey = Color.Magenta; + this.DoubleBuffered = true; + + // 初始大小 + this.Size = new Size(200, 100); + + // 添加关闭按钮 + var closeButton = new Button(); + closeButton.Text = "×"; + closeButton.FlatStyle = FlatStyle.Flat; + closeButton.FlatAppearance.BorderSize = 0; + closeButton.BackColor = Color.Transparent; + closeButton.ForeColor = Color.Gray; + closeButton.Font = new Font("Arial", 8, FontStyle.Bold); + closeButton.Size = new Size(20, 20); + closeButton.Location = new Point(this.Width - closeButton.Width - 5, 5); + closeButton.Click += (s, e) => this.Hide(); + this.Controls.Add(closeButton); + } + public void UpdateSize() { + CalculateSize(); + } + + private void CalculateSize() { + using (Graphics g = this.CreateGraphics()) { + // 增加最大宽度限制,防止气泡过宽 + int maxWidth = Screen.PrimaryScreen.WorkingArea.Width / 3; + SizeF textSize = g.MeasureString(_message, messageFont, maxWidth); + + int newWidth = (int)Math.Max(150, Math.Min(maxWidth, textSize.Width + this.Padding.Horizontal)); + int newHeight = (int)(textSize.Height + this.Padding.Vertical); + + this.Size = new Size(newWidth, newHeight); + + if (this.Controls.Count > 0) { + this.Controls[0].Location = new Point(this.Width - 25, 5); + } + } + } + + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + Graphics g = e.Graphics; + g.SmoothingMode = SmoothingMode.AntiAlias; + + // 创建气泡路径 + GraphicsPath path = new GraphicsPath(); + Rectangle mainRect = new Rectangle(0, 0, this.Width, this.Height - tailHeight); + + // 绘制圆角矩形主体 + path.AddArc(mainRect.X, mainRect.Y, cornerRadius, cornerRadius, 180, 90); + path.AddArc(mainRect.X + mainRect.Width - cornerRadius, mainRect.Y, cornerRadius, cornerRadius, 270, 90); + path.AddArc(mainRect.X + mainRect.Width - cornerRadius, mainRect.Y + mainRect.Height - cornerRadius, cornerRadius, cornerRadius, 0, 90); + + // 添加尾巴 + int tailWidth = 20; + int tailX = this.Width / 2 - tailWidth / 2; + path.AddLine(mainRect.X + mainRect.Width - cornerRadius, mainRect.Y + mainRect.Height, tailX + tailWidth, mainRect.Y + mainRect.Height); + path.AddLine(tailX + tailWidth, mainRect.Y + mainRect.Height, this.Width / 2, this.Height); + path.AddLine(this.Width / 2, this.Height, tailX, mainRect.Y + mainRect.Height); + path.AddLine(tailX, mainRect.Y + mainRect.Height, mainRect.X + cornerRadius, mainRect.Y + mainRect.Height); + + path.AddArc(mainRect.X, mainRect.Y + mainRect.Height - cornerRadius, cornerRadius, cornerRadius, 90, 90); + + // 填充气泡 + using (Brush bubbleBrush = new SolidBrush(bubbleColor)) { + g.FillPath(bubbleBrush, path); + } + + // 绘制边框 + using (Pen borderPen = new Pen(borderColor, 1)) { + g.DrawPath(borderPen, path); + } + + // 绘制文本 + using (Brush textBrush = new SolidBrush(Color.Black)) { + g.DrawString(_message, messageFont, textBrush, + new RectangleF( + this.Padding.Left, + this.Padding.Top, + this.Width - this.Padding.Horizontal, + this.Height - this.Padding.Vertical)); + } + } + + private void InitializeComponent() { + this.SuspendLayout(); + // + // BubbleForm + // + this.ClientSize = new System.Drawing.Size(284, 261); + this.Name = "BubbleForm"; + this.ResumeLayout(false); + + } + } +} diff --git a/AccompanyingAssistant/BubbleForm.resx b/AccompanyingAssistant/BubbleForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/AccompanyingAssistant/BubbleForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + \ No newline at end of file diff --git a/AccompanyingAssistant/MainForm.Designer.cs b/AccompanyingAssistant/MainForm.Designer.cs new file mode 100644 index 0000000..5729eea --- /dev/null +++ b/AccompanyingAssistant/MainForm.Designer.cs @@ -0,0 +1,191 @@ +namespace AccompanyingAssistant { + partial class MainForm { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); + this.tmrDrag = new System.Windows.Forms.Timer(this.components); + this.rightClickMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.退出ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.眨眼ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.衣柜ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.圣诞帽子ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.樱花帽子ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.水手帽ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.风车帽子ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.圣诞衣服ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.和服ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.tmrBlink = new System.Windows.Forms.Timer(this.components); + this.tmrBubble = new System.Windows.Forms.Timer(this.components); + this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components); + this.消息测试ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.rightClickMenu.SuspendLayout(); + this.SuspendLayout(); + // + // tmrDrag + // + this.tmrDrag.Tick += new System.EventHandler(this.tmrDrag_Tick); + // + // rightClickMenu + // + this.rightClickMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.退出ToolStripMenuItem, + this.眨眼ToolStripMenuItem, + this.衣柜ToolStripMenuItem, + this.消息测试ToolStripMenuItem}); + this.rightClickMenu.Name = "rightClickMenu"; + this.rightClickMenu.Size = new System.Drawing.Size(181, 114); + // + // 退出ToolStripMenuItem + // + this.退出ToolStripMenuItem.Name = "退出ToolStripMenuItem"; + this.退出ToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.退出ToolStripMenuItem.Text = "退出"; + this.退出ToolStripMenuItem.Click += new System.EventHandler(this.退出ToolStripMenuItem_Click); + // + // 眨眼ToolStripMenuItem + // + this.眨眼ToolStripMenuItem.Name = "眨眼ToolStripMenuItem"; + this.眨眼ToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.眨眼ToolStripMenuItem.Text = "眨眼"; + this.眨眼ToolStripMenuItem.Click += new System.EventHandler(this.眨眼ToolStripMenuItem_Click); + // + // 衣柜ToolStripMenuItem + // + this.衣柜ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.圣诞帽子ToolStripMenuItem, + this.樱花帽子ToolStripMenuItem, + this.水手帽ToolStripMenuItem, + this.风车帽子ToolStripMenuItem, + this.圣诞衣服ToolStripMenuItem, + this.和服ToolStripMenuItem}); + this.衣柜ToolStripMenuItem.Name = "衣柜ToolStripMenuItem"; + this.衣柜ToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.衣柜ToolStripMenuItem.Text = "衣柜"; + // + // 圣诞帽子ToolStripMenuItem + // + this.圣诞帽子ToolStripMenuItem.Name = "圣诞帽子ToolStripMenuItem"; + this.圣诞帽子ToolStripMenuItem.Size = new System.Drawing.Size(157, 22); + this.圣诞帽子ToolStripMenuItem.Text = "帽子 — 圣诞"; + this.圣诞帽子ToolStripMenuItem.Click += new System.EventHandler(this.帽子ToolStripMenuItem_Click); + // + // 樱花帽子ToolStripMenuItem + // + this.樱花帽子ToolStripMenuItem.Name = "樱花帽子ToolStripMenuItem"; + this.樱花帽子ToolStripMenuItem.Size = new System.Drawing.Size(157, 22); + this.樱花帽子ToolStripMenuItem.Text = "帽子 — 樱花"; + this.樱花帽子ToolStripMenuItem.Click += new System.EventHandler(this.帽子ToolStripMenuItem_Click); + // + // 水手帽ToolStripMenuItem + // + this.水手帽ToolStripMenuItem.Name = "水手帽ToolStripMenuItem"; + this.水手帽ToolStripMenuItem.Size = new System.Drawing.Size(157, 22); + this.水手帽ToolStripMenuItem.Text = "帽子 — 水手帽"; + this.水手帽ToolStripMenuItem.Click += new System.EventHandler(this.帽子ToolStripMenuItem_Click); + // + // 风车帽子ToolStripMenuItem + // + this.风车帽子ToolStripMenuItem.Name = "风车帽子ToolStripMenuItem"; + this.风车帽子ToolStripMenuItem.Size = new System.Drawing.Size(157, 22); + this.风车帽子ToolStripMenuItem.Text = "帽子 — 风车"; + this.风车帽子ToolStripMenuItem.Click += new System.EventHandler(this.帽子ToolStripMenuItem_Click); + // + // 圣诞衣服ToolStripMenuItem + // + this.圣诞衣服ToolStripMenuItem.Name = "圣诞衣服ToolStripMenuItem"; + this.圣诞衣服ToolStripMenuItem.Size = new System.Drawing.Size(157, 22); + this.圣诞衣服ToolStripMenuItem.Text = "衣服 — 圣诞"; + this.圣诞衣服ToolStripMenuItem.Click += new System.EventHandler(this.衣服ToolStripMenuItem_Click); + // + // 和服ToolStripMenuItem + // + this.和服ToolStripMenuItem.Name = "和服ToolStripMenuItem"; + this.和服ToolStripMenuItem.Size = new System.Drawing.Size(157, 22); + this.和服ToolStripMenuItem.Text = "衣服 — 和服"; + this.和服ToolStripMenuItem.Click += new System.EventHandler(this.衣服ToolStripMenuItem_Click); + // + // tmrBlink + // + this.tmrBlink.Tick += new System.EventHandler(this.tmrBlink_Tick); + // + // tmrBubble + // + this.tmrBubble.Tick += new System.EventHandler(this.tmrBubble_Tick); + // + // notifyIcon1 + // + this.notifyIcon1.ContextMenuStrip = this.rightClickMenu; + this.notifyIcon1.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon1.Icon"))); + this.notifyIcon1.Text = "notifyIcon1"; + this.notifyIcon1.Visible = true; + // + // 消息测试ToolStripMenuItem + // + this.消息测试ToolStripMenuItem.Name = "消息测试ToolStripMenuItem"; + this.消息测试ToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.消息测试ToolStripMenuItem.Text = "消息测试"; + this.消息测试ToolStripMenuItem.Click += new System.EventHandler(this.消息测试ToolStripMenuItem_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(266, 242); + this.ContextMenuStrip = this.rightClickMenu; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Name = "Form1"; + this.ShowInTaskbar = false; + this.Text = "Form1"; + this.TopMost = true; + this.Load += new System.EventHandler(this.Form1_Load); + this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown); + this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseMove); + this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseUp); + this.rightClickMenu.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Timer tmrDrag; + private System.Windows.Forms.ContextMenuStrip rightClickMenu; + private System.Windows.Forms.ToolStripMenuItem 眨眼ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 退出ToolStripMenuItem; + private System.Windows.Forms.Timer tmrBlink; + private System.Windows.Forms.Timer tmrBubble; + private System.Windows.Forms.NotifyIcon notifyIcon1; + private System.Windows.Forms.ToolStripMenuItem 衣柜ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 圣诞帽子ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 圣诞衣服ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 樱花帽子ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 水手帽ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 和服ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 风车帽子ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 消息测试ToolStripMenuItem; + } +} + diff --git a/AccompanyingAssistant/MainForm.cs b/AccompanyingAssistant/MainForm.cs new file mode 100644 index 0000000..0004822 --- /dev/null +++ b/AccompanyingAssistant/MainForm.cs @@ -0,0 +1,342 @@ +using System.ComponentModel; + +namespace AccompanyingAssistant { + public partial class MainForm : Form { + bool haveHandle = true; //SetBits + bool bFormDragging = false; // ڴƶ + Point oPointClicked; // ڴƶ + + int dragFrame = 0; //ק֡ + int blinkFrame = 0; //գ֡ + int hatNum = -1; + int clothesNum = -1; + private enum PetStates { General = 0, Drag = 1 }; + + Bitmap[] pet = new Bitmap[30]; + Bitmap[] petDrag = new Bitmap[3]; + Bitmap[] petBlink = new Bitmap[2]; + + Bitmap[,] petHat = new Bitmap[10, 2]; + Bitmap[,] petClothes = new Bitmap[5, 4]; + + Bitmap[] petWithClothes = new Bitmap[30]; + Bitmap[] petDragWithClothes = new Bitmap[3]; + Bitmap[] petBlinkWithClothes = new Bitmap[3]; + + private Queue messageQueue = new Queue(); + private bool isBubbleVisible = false; + private int bubbleShowTime = 5000; // ʾʱ() + private DateTime bubbleStartTime; + private BubbleForm bubbleForm; + + public MainForm() { + InitializeComponent(); + } + #region + + protected override void OnClosing(CancelEventArgs e) { + e.Cancel = true; + base.OnClosing(e); + haveHandle = false; + } + + protected override void OnHandleCreated(EventArgs e) { + InitializeStyles(); + base.OnHandleCreated(e); + haveHandle = true; + } + + private void InitializeStyles() { + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.UserPaint, true); + UpdateStyles(); + } + + protected override CreateParams CreateParams { + get { + CreateParams cParms = base.CreateParams; + cParms.ExStyle |= 0x00080000; // WS_EX_LAYERED + return cParms; + } + } + + #endregion + + public void SetBits(Bitmap bitmap) { + if (!haveHandle) return; + + if (!Bitmap.IsCanonicalPixelFormat(bitmap.PixelFormat) || !Bitmap.IsAlphaPixelFormat(bitmap.PixelFormat)) + MessageBox.Show("Error Bitmap"); + + IntPtr oldBits = IntPtr.Zero; + IntPtr screenDC = Win32.GetDC(IntPtr.Zero); + IntPtr hBitmap = IntPtr.Zero; + IntPtr memDc = Win32.CreateCompatibleDC(screenDC); + + try { + Win32.Point topLoc = new Win32.Point(Left, Top); + Win32.Size bitMapSize = new Win32.Size(bitmap.Width, bitmap.Height); + Win32.BLENDFUNCTION blendFunc = new Win32.BLENDFUNCTION(); + Win32.Point srcLoc = new Win32.Point(0, 0); + + hBitmap = bitmap.GetHbitmap(Color.FromArgb(0)); + oldBits = Win32.SelectObject(memDc, hBitmap); + + blendFunc.BlendOp = Win32.AC_SRC_OVER; + blendFunc.SourceConstantAlpha = 255; + blendFunc.AlphaFormat = Win32.AC_SRC_ALPHA; + blendFunc.BlendFlags = 0; + + Win32.UpdateLayeredWindow(Handle, screenDC, ref topLoc, ref bitMapSize, memDc, ref srcLoc, 0, ref blendFunc, Win32.ULW_ALPHA); + } finally { + if (hBitmap != IntPtr.Zero) { + Win32.SelectObject(memDc, oldBits); + Win32.DeleteObject(hBitmap); + } + Win32.ReleaseDC(IntPtr.Zero, screenDC); + Win32.DeleteDC(memDc); + } + } + + private Bitmap CombinedPic(Bitmap bottom, Bitmap top, int x, int y) { + Bitmap bitmap = new Bitmap(bottom.Width, bottom.Height); + Graphics g = Graphics.FromImage(bitmap); + g.DrawImage(bottom, new Rectangle(0, 0, bottom.Width, bottom.Height), new Rectangle(0, 0, bottom.Width, bottom.Height), GraphicsUnit.Pixel); + g.DrawImage(top, new Rectangle(x, y, top.Width, top.Height), new Rectangle(0, 0, top.Width, top.Height), GraphicsUnit.Pixel); + return bitmap; + } + + private Bitmap Dress(Bitmap img, int state) { + Bitmap bitmap = new Bitmap(img.Width, img.Height); + bitmap = img; + if (clothesNum != -1) { + bitmap = CombinedPic(bitmap, petClothes[clothesNum, state], 0, 0); + } + if (hatNum != -1) { + bitmap = CombinedPic(bitmap, petHat[hatNum, state], 0, 0); + } + return bitmap; + } + protected override void OnLocationChanged(EventArgs e) { + base.OnLocationChanged(e); + if (isBubbleVisible) { + bubbleForm.Location = CalculateBubblePosition(); + } + } + + private void Form1_Load(object sender, EventArgs e) { + pet[0] = new Bitmap(Application.StartupPath + "\\shell\\surface0000.png"); + pet[1] = new Bitmap(Application.StartupPath + "\\shell\\surface0001.png"); + pet[2] = new Bitmap(Application.StartupPath + "\\shell\\surface0002.png"); + pet[3] = new Bitmap(Application.StartupPath + "\\shell\\surface0003.png"); + pet[4] = new Bitmap(Application.StartupPath + "\\shell\\surface0004.png"); + pet[5] = new Bitmap(Application.StartupPath + "\\shell\\surface0005.png"); + pet[6] = new Bitmap(Application.StartupPath + "\\shell\\surface0006.png"); + pet[7] = new Bitmap(Application.StartupPath + "\\shell\\surface0007.png"); + pet[8] = new Bitmap(Application.StartupPath + "\\shell\\surface0008.png"); + pet[9] = new Bitmap(Application.StartupPath + "\\shell\\surface0009.png"); + petDrag[0] = new Bitmap(Application.StartupPath + "\\shell\\surface0091.png"); + petDrag[1] = new Bitmap(Application.StartupPath + "\\shell\\surface0092.png"); + petDrag[2] = new Bitmap(Application.StartupPath + "\\shell\\surface0093.png"); + petBlink[0] = new Bitmap(Application.StartupPath + "\\shell\\surface1003.png"); + petBlink[1] = new Bitmap(Application.StartupPath + "\\shell\\surface1004.png"); + petHat[0, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3000.png"); + petHat[0, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3001.png"); + petHat[1, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3002.png"); + petHat[1, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3003.png"); + petHat[2, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3004.png"); + petHat[2, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3005.png"); + petHat[3, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3006.png"); + petHat[3, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3007.png"); + petClothes[0, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3100.png"); + petClothes[0, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3101.png"); + petClothes[0, 2] = new Bitmap(Application.StartupPath + "\\shell\\surface3102.png"); + petClothes[0, 3] = new Bitmap(Application.StartupPath + "\\shell\\surface3103.png"); + petClothes[1, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3200.png"); + petClothes[1, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3201.png"); + petClothes[1, 2] = new Bitmap(Application.StartupPath + "\\shell\\surface3202.png"); + petClothes[1, 3] = new Bitmap(Application.StartupPath + "\\shell\\surface3203.png"); + + DressAll(); + SetBits(petWithClothes[0]); + // ʼݴ + InitializeBubbleForm(); + + } + // ʼݴ + private void InitializeBubbleForm() { + bubbleForm = new BubbleForm(); + bubbleForm.Visible = false; + bubbleForm.Owner = this; + bubbleForm.Location = CalculateBubblePosition(); + } + + // λãڳϷ + private Point CalculateBubblePosition() { + // ȡﴰĵ + Point petCenter = new Point( + this.Left + (this.Width / 2), + this.Top + ); + + // λãڳϷ + return new Point( + petCenter.X - (bubbleForm.Width / 2), + petCenter.Y - bubbleForm.Height - 10 + ); + } + + // ʾϢ + public void ShowMessage(string message) { + if (isBubbleVisible) { + messageQueue.Enqueue(message); + return; + } + + bubbleForm.Message = message; + + // ȷݴСѸ + bubbleForm.UpdateSize(); + + // ȷλ + Point bubblePos = CalculateBubblePosition(); + bubbleForm.Location = bubblePos; + + bubbleForm.Show(); + bubbleForm.BringToFront(); + isBubbleVisible = true; + bubbleStartTime = DateTime.Now; + + tmrBubble.Interval = 100; + tmrBubble.Enabled = true; + } + + // رյǰ + public void CloseBubble() { + bubbleForm.Hide(); + isBubbleVisible = false; + + // ǷдʾϢ + if (messageQueue.Count > 0) { + ShowMessage(messageQueue.Dequeue()); + } + } + + // ݼʱTick¼ + private void tmrBubble_Tick(object sender, EventArgs e) { + // Ƿﵽʾʱ + if ((DateTime.Now - bubbleStartTime).TotalMilliseconds >= bubbleShowTime) { + CloseBubble(); + } + } + + + #region ק + + private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) { + if (e.Button == System.Windows.Forms.MouseButtons.Left) { + bFormDragging = true; + oPointClicked = new Point(e.X, e.Y); + tmrDrag.Interval = 110; + tmrDrag.Enabled = true; + } + } + + private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) { + if (e.Button == System.Windows.Forms.MouseButtons.Left) { + bFormDragging = false; + tmrDrag.Enabled = false; + SetBits(petWithClothes[0]); + } + } + + private void Form1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) { + if (bFormDragging) { + Point oMoveToPoint = default(Point); + //ԵǰλΪҳĿλ + oMoveToPoint = PointToScreen(new Point(e.X, e.Y)); + oMoveToPoint.Offset(oPointClicked.X * -1, (oPointClicked.Y + SystemInformation.CaptionHeight + SystemInformation.BorderSize.Height) * -1 + 24); + Location = oMoveToPoint; + } + } + + private void tmrDrag_Tick(object sender, EventArgs e) { + if (dragFrame < 2) { + SetBits(petDragWithClothes[dragFrame]); + dragFrame += 1; + } else { + SetBits(petDragWithClothes[dragFrame]); + dragFrame = 0; + } + } + + #endregion + + private void ˳ToolStripMenuItem_Click(object sender, EventArgs e) { + Application.Exit(); + } + + private void գToolStripMenuItem_Click(object sender, EventArgs e) { + tmrBlink.Interval = 40; + tmrBlink.Start(); + } + + private void tmrBlink_Tick(object sender, EventArgs e) { + if (blinkFrame < 2) { + SetBits(petBlinkWithClothes[blinkFrame]); + blinkFrame += 1; + } else { + SetBits(petWithClothes[0]); + blinkFrame = 0; + tmrBlink.Stop(); + } + } + + private void ñToolStripMenuItem_Click(object sender, EventArgs e) { + bool itemChecked = (sender as ToolStripMenuItem).Checked; + if (itemChecked) { + hatNum = -1; + } else { + hatNum = this.¹ToolStripMenuItem.DropDownItems.IndexOf(sender as ToolStripMenuItem); + } + this.ʥñToolStripMenuItem.Checked = false; + this.ӣñToolStripMenuItem.Checked = false; + this.ˮñToolStripMenuItem.Checked = false; + this.糵ñToolStripMenuItem.Checked = false; + (sender as ToolStripMenuItem).Checked = !itemChecked; + DressAll(); + SetBits(petWithClothes[0]); + } + + private void ·ToolStripMenuItem_Click(object sender, EventArgs e) { + bool itemChecked = (sender as ToolStripMenuItem).Checked; + if (itemChecked) { + clothesNum = -1; + } else { + clothesNum = this.¹ToolStripMenuItem.DropDownItems.IndexOf(sender as ToolStripMenuItem) - 4; + } + this.ʥ·ToolStripMenuItem.Checked = false; + this.ͷToolStripMenuItem.Checked = false; + (sender as ToolStripMenuItem).Checked = !itemChecked; + DressAll(); + SetBits(petWithClothes[0]); + } + + private void DressAll() { + int i; + for (i = 0; i < 10; i++) { + petWithClothes[i] = Dress(pet[i], (int)PetStates.General); + } + for (i = 0; i < 3; i++) { + petDragWithClothes[i] = Dress(petDrag[i], (int)PetStates.Drag); + } + for (i = 0; i < 2; i++) { + petBlinkWithClothes[i] = Dress(petBlink[i], (int)PetStates.General); + } + } + + private void ϢToolStripMenuItem_Click(object sender, EventArgs e) { + ShowMessage("һϢݣ\n5Զʧ\nԵҼ鿴ѡ"); + } + } +} diff --git a/AccompanyingAssistant/MainForm.resx b/AccompanyingAssistant/MainForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/AccompanyingAssistant/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + \ No newline at end of file diff --git a/AccompanyingAssistant/Program.cs b/AccompanyingAssistant/Program.cs new file mode 100644 index 0000000..9e60a55 --- /dev/null +++ b/AccompanyingAssistant/Program.cs @@ -0,0 +1,14 @@ +namespace AccompanyingAssistant { + internal static class Program { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new MainForm()); + } + } +} \ No newline at end of file diff --git a/AccompanyingAssistant/Win32.cs b/AccompanyingAssistant/Win32.cs new file mode 100644 index 0000000..bbbb97a --- /dev/null +++ b/AccompanyingAssistant/Win32.cs @@ -0,0 +1,67 @@ +namespace AccompanyingAssistant { + using System; + using System.Runtime.InteropServices; + + internal class Win32 { + public const byte AC_SRC_ALPHA = 1; + public const byte AC_SRC_OVER = 0; + public const int ULW_ALPHA = 2; + + [DllImport("gdi32.dll")] + public static extern IntPtr CreateCompatibleDC(IntPtr hDC); + [DllImport("gdi32.dll")] + public static extern Bool DeleteDC(IntPtr hdc); + [DllImport("gdi32.dll")] + public static extern Bool DeleteObject(IntPtr hObject); + [DllImport("user32.dll")] + public static extern IntPtr GetDC(IntPtr handle); + [DllImport("user32.dll", ExactSpelling = true)] + public static extern int ReleaseDC(IntPtr handle, IntPtr hDC); + [DllImport("gdi32.dll")] + public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); + [DllImport("user32.dll")] + public static extern Bool UpdateLayeredWindow(IntPtr handle, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags); + + [DllImport("user32.dll")] + public static extern Bool ReleaseCapture(); + + [DllImport("user32.dll")] + public static extern Bool SendMessage(IntPtr hWnd, int msg, int wParam, int lParam); + + [StructLayout(LayoutKind.Sequential)] + public struct BLENDFUNCTION { + public byte BlendOp; + public byte BlendFlags; + public byte SourceConstantAlpha; + public byte AlphaFormat; + } + + public enum Bool { + False, + True + } + + [StructLayout(LayoutKind.Sequential)] + public struct Point { + public int x; + public int y; + public Point(int x, int y) { + this = new Win32.Point(); + this.x = x; + this.y = y; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct Size { + public int cx; + public int cy; + public Size(int cx, int cy) { + this = new Win32.Size(); + this.cx = cx; + this.cy = cy; + } + } + } +} + diff --git a/AccompanyingAssistant/WindowManager.cs b/AccompanyingAssistant/WindowManager.cs new file mode 100644 index 0000000..7ce2a39 --- /dev/null +++ b/AccompanyingAssistant/WindowManager.cs @@ -0,0 +1,51 @@ +using AccompanyingAssistant; + +public class WindowManager { + private MainForm? mainForm = null; + private Thread? uiThread = null; + + public void ShowMain() { + if (mainForm == null || mainForm.IsDisposed) { + uiThread = new Thread(() => { + ApplicationConfiguration.Initialize(); + mainForm = new MainForm(); + Application.Run(mainForm); + }); + uiThread.SetApartmentState(ApartmentState.STA); + uiThread.IsBackground = true; + uiThread.Start(); + + // 等待窗体创建完毕(简单阻塞) + while (mainForm == null || !mainForm.IsHandleCreated) { + Thread.Sleep(10); + } + } else { + mainForm.Invoke(() => mainForm.Show()); + } + } + + public void ShowBubble(string message) { + ShowMain(); + + // 此处保证 ShowMessage 在 UI 线程上执行 + if (mainForm != null && mainForm.IsHandleCreated) { + mainForm.Invoke(() => mainForm.ShowMessage(message)); + } + } + + public void CloseBubble() { + if (mainForm != null && mainForm.IsHandleCreated) { + mainForm.Invoke(() => mainForm.CloseBubble()); + } + } + + public void CloseMain() { + if (mainForm != null && mainForm.IsHandleCreated) { + mainForm.Invoke(() => { + mainForm.Close(); + mainForm.Dispose(); + mainForm = null; + }); + } + } +} diff --git a/AccompanyingAssistant/shell/surface0000.png b/AccompanyingAssistant/shell/surface0000.png new file mode 100644 index 0000000..5e12127 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0000.png differ diff --git a/AccompanyingAssistant/shell/surface0000_2001.png b/AccompanyingAssistant/shell/surface0000_2001.png new file mode 100644 index 0000000..751ba3d Binary files /dev/null and b/AccompanyingAssistant/shell/surface0000_2001.png differ diff --git a/AccompanyingAssistant/shell/surface0000_2002.png b/AccompanyingAssistant/shell/surface0000_2002.png new file mode 100644 index 0000000..f3343a5 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0000_2002.png differ diff --git a/AccompanyingAssistant/shell/surface0000_2004.png b/AccompanyingAssistant/shell/surface0000_2004.png new file mode 100644 index 0000000..72c0f03 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0000_2004.png differ diff --git a/AccompanyingAssistant/shell/surface0001.png b/AccompanyingAssistant/shell/surface0001.png new file mode 100644 index 0000000..435712b Binary files /dev/null and b/AccompanyingAssistant/shell/surface0001.png differ diff --git a/AccompanyingAssistant/shell/surface0002.png b/AccompanyingAssistant/shell/surface0002.png new file mode 100644 index 0000000..7c84db3 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0002.png differ diff --git a/AccompanyingAssistant/shell/surface0003.png b/AccompanyingAssistant/shell/surface0003.png new file mode 100644 index 0000000..6aaf03d Binary files /dev/null and b/AccompanyingAssistant/shell/surface0003.png differ diff --git a/AccompanyingAssistant/shell/surface0004.png b/AccompanyingAssistant/shell/surface0004.png new file mode 100644 index 0000000..e6c6077 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0004.png differ diff --git a/AccompanyingAssistant/shell/surface0005.png b/AccompanyingAssistant/shell/surface0005.png new file mode 100644 index 0000000..508a4c5 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0005.png differ diff --git a/AccompanyingAssistant/shell/surface0006.png b/AccompanyingAssistant/shell/surface0006.png new file mode 100644 index 0000000..a3398f4 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0006.png differ diff --git a/AccompanyingAssistant/shell/surface0007.png b/AccompanyingAssistant/shell/surface0007.png new file mode 100644 index 0000000..bac1d00 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0007.png differ diff --git a/AccompanyingAssistant/shell/surface0008.png b/AccompanyingAssistant/shell/surface0008.png new file mode 100644 index 0000000..c39a250 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0008.png differ diff --git a/AccompanyingAssistant/shell/surface0009.png b/AccompanyingAssistant/shell/surface0009.png new file mode 100644 index 0000000..be74667 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0009.png differ diff --git a/AccompanyingAssistant/shell/surface0010.png b/AccompanyingAssistant/shell/surface0010.png new file mode 100644 index 0000000..330a4f6 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0010.png differ diff --git a/AccompanyingAssistant/shell/surface0020.png b/AccompanyingAssistant/shell/surface0020.png new file mode 100644 index 0000000..d90f5f2 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0020.png differ diff --git a/AccompanyingAssistant/shell/surface0025.png b/AccompanyingAssistant/shell/surface0025.png new file mode 100644 index 0000000..418365f Binary files /dev/null and b/AccompanyingAssistant/shell/surface0025.png differ diff --git a/AccompanyingAssistant/shell/surface0029.png b/AccompanyingAssistant/shell/surface0029.png new file mode 100644 index 0000000..8b0db64 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0029.png differ diff --git a/AccompanyingAssistant/shell/surface0030.png b/AccompanyingAssistant/shell/surface0030.png new file mode 100644 index 0000000..4f97617 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0030.png differ diff --git a/AccompanyingAssistant/shell/surface0032.png b/AccompanyingAssistant/shell/surface0032.png new file mode 100644 index 0000000..faaf243 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0032.png differ diff --git a/AccompanyingAssistant/shell/surface0033.png b/AccompanyingAssistant/shell/surface0033.png new file mode 100644 index 0000000..1ae612c Binary files /dev/null and b/AccompanyingAssistant/shell/surface0033.png differ diff --git a/AccompanyingAssistant/shell/surface0034.png b/AccompanyingAssistant/shell/surface0034.png new file mode 100644 index 0000000..dadce2a Binary files /dev/null and b/AccompanyingAssistant/shell/surface0034.png differ diff --git a/AccompanyingAssistant/shell/surface0035.png b/AccompanyingAssistant/shell/surface0035.png new file mode 100644 index 0000000..0d47575 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0035.png differ diff --git a/AccompanyingAssistant/shell/surface0036.png b/AccompanyingAssistant/shell/surface0036.png new file mode 100644 index 0000000..1619a36 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0036.png differ diff --git a/AccompanyingAssistant/shell/surface0037.png b/AccompanyingAssistant/shell/surface0037.png new file mode 100644 index 0000000..1781772 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0037.png differ diff --git a/AccompanyingAssistant/shell/surface0038.png b/AccompanyingAssistant/shell/surface0038.png new file mode 100644 index 0000000..7b3d32d Binary files /dev/null and b/AccompanyingAssistant/shell/surface0038.png differ diff --git a/AccompanyingAssistant/shell/surface0042.png b/AccompanyingAssistant/shell/surface0042.png new file mode 100644 index 0000000..b954182 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0042.png differ diff --git a/AccompanyingAssistant/shell/surface0043.png b/AccompanyingAssistant/shell/surface0043.png new file mode 100644 index 0000000..bf1dd17 Binary files /dev/null and b/AccompanyingAssistant/shell/surface0043.png differ diff --git a/AccompanyingAssistant/shell/surface0044.png b/AccompanyingAssistant/shell/surface0044.png new file mode 100644 index 0000000..01d34fb Binary files /dev/null and b/AccompanyingAssistant/shell/surface0044.png differ diff --git a/AccompanyingAssistant/shell/surface0091.png b/AccompanyingAssistant/shell/surface0091.png new file mode 100644 index 0000000..e74103f Binary files /dev/null and b/AccompanyingAssistant/shell/surface0091.png differ diff --git a/AccompanyingAssistant/shell/surface0092.png b/AccompanyingAssistant/shell/surface0092.png new file mode 100644 index 0000000..c5cd04b Binary files /dev/null and b/AccompanyingAssistant/shell/surface0092.png differ diff --git a/AccompanyingAssistant/shell/surface0093.png b/AccompanyingAssistant/shell/surface0093.png new file mode 100644 index 0000000..a58942c Binary files /dev/null and b/AccompanyingAssistant/shell/surface0093.png differ diff --git a/AccompanyingAssistant/shell/surface1001.png b/AccompanyingAssistant/shell/surface1001.png new file mode 100644 index 0000000..7aba190 Binary files /dev/null and b/AccompanyingAssistant/shell/surface1001.png differ diff --git a/AccompanyingAssistant/shell/surface1002.png b/AccompanyingAssistant/shell/surface1002.png new file mode 100644 index 0000000..5d6292c Binary files /dev/null and b/AccompanyingAssistant/shell/surface1002.png differ diff --git a/AccompanyingAssistant/shell/surface1003.png b/AccompanyingAssistant/shell/surface1003.png new file mode 100644 index 0000000..f9c547e Binary files /dev/null and b/AccompanyingAssistant/shell/surface1003.png differ diff --git a/AccompanyingAssistant/shell/surface1004.png b/AccompanyingAssistant/shell/surface1004.png new file mode 100644 index 0000000..38af0c2 Binary files /dev/null and b/AccompanyingAssistant/shell/surface1004.png differ diff --git a/AccompanyingAssistant/shell/surface2001.png b/AccompanyingAssistant/shell/surface2001.png new file mode 100644 index 0000000..5d3f1a1 Binary files /dev/null and b/AccompanyingAssistant/shell/surface2001.png differ diff --git a/AccompanyingAssistant/shell/surface2002.png b/AccompanyingAssistant/shell/surface2002.png new file mode 100644 index 0000000..3a75f65 Binary files /dev/null and b/AccompanyingAssistant/shell/surface2002.png differ diff --git a/AccompanyingAssistant/shell/surface2003.png b/AccompanyingAssistant/shell/surface2003.png new file mode 100644 index 0000000..902c33e Binary files /dev/null and b/AccompanyingAssistant/shell/surface2003.png differ diff --git a/AccompanyingAssistant/shell/surface2004.png b/AccompanyingAssistant/shell/surface2004.png new file mode 100644 index 0000000..d95fe91 Binary files /dev/null and b/AccompanyingAssistant/shell/surface2004.png differ diff --git a/AccompanyingAssistant/shell/surface3000.png b/AccompanyingAssistant/shell/surface3000.png new file mode 100644 index 0000000..01bb660 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3000.png differ diff --git a/AccompanyingAssistant/shell/surface3001.png b/AccompanyingAssistant/shell/surface3001.png new file mode 100644 index 0000000..ca5a868 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3001.png differ diff --git a/AccompanyingAssistant/shell/surface3002.png b/AccompanyingAssistant/shell/surface3002.png new file mode 100644 index 0000000..52e2c5c Binary files /dev/null and b/AccompanyingAssistant/shell/surface3002.png differ diff --git a/AccompanyingAssistant/shell/surface3003.png b/AccompanyingAssistant/shell/surface3003.png new file mode 100644 index 0000000..3d22e2c Binary files /dev/null and b/AccompanyingAssistant/shell/surface3003.png differ diff --git a/AccompanyingAssistant/shell/surface3004.png b/AccompanyingAssistant/shell/surface3004.png new file mode 100644 index 0000000..2efe0ad Binary files /dev/null and b/AccompanyingAssistant/shell/surface3004.png differ diff --git a/AccompanyingAssistant/shell/surface3005.png b/AccompanyingAssistant/shell/surface3005.png new file mode 100644 index 0000000..64e5097 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3005.png differ diff --git a/AccompanyingAssistant/shell/surface3006.png b/AccompanyingAssistant/shell/surface3006.png new file mode 100644 index 0000000..009fc58 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3006.png differ diff --git a/AccompanyingAssistant/shell/surface3007.png b/AccompanyingAssistant/shell/surface3007.png new file mode 100644 index 0000000..38b5c66 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3007.png differ diff --git a/AccompanyingAssistant/shell/surface3100.png b/AccompanyingAssistant/shell/surface3100.png new file mode 100644 index 0000000..6f4fbde Binary files /dev/null and b/AccompanyingAssistant/shell/surface3100.png differ diff --git a/AccompanyingAssistant/shell/surface3101.png b/AccompanyingAssistant/shell/surface3101.png new file mode 100644 index 0000000..44b7126 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3101.png differ diff --git a/AccompanyingAssistant/shell/surface3102.png b/AccompanyingAssistant/shell/surface3102.png new file mode 100644 index 0000000..8d395ec Binary files /dev/null and b/AccompanyingAssistant/shell/surface3102.png differ diff --git a/AccompanyingAssistant/shell/surface3103.png b/AccompanyingAssistant/shell/surface3103.png new file mode 100644 index 0000000..61c5d37 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3103.png differ diff --git a/AccompanyingAssistant/shell/surface3104.png b/AccompanyingAssistant/shell/surface3104.png new file mode 100644 index 0000000..64f8663 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3104.png differ diff --git a/AccompanyingAssistant/shell/surface3200.png b/AccompanyingAssistant/shell/surface3200.png new file mode 100644 index 0000000..c70cb6b Binary files /dev/null and b/AccompanyingAssistant/shell/surface3200.png differ diff --git a/AccompanyingAssistant/shell/surface3201.png b/AccompanyingAssistant/shell/surface3201.png new file mode 100644 index 0000000..17a1a77 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3201.png differ diff --git a/AccompanyingAssistant/shell/surface3202.png b/AccompanyingAssistant/shell/surface3202.png new file mode 100644 index 0000000..70b5b45 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3202.png differ diff --git a/AccompanyingAssistant/shell/surface3203.png b/AccompanyingAssistant/shell/surface3203.png new file mode 100644 index 0000000..22d49f4 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3203.png differ diff --git a/AccompanyingAssistant/shell/surface3204.png b/AccompanyingAssistant/shell/surface3204.png new file mode 100644 index 0000000..8b2f826 Binary files /dev/null and b/AccompanyingAssistant/shell/surface3204.png differ diff --git a/AccompanyingAssistant/shell/surface9999.png b/AccompanyingAssistant/shell/surface9999.png new file mode 100644 index 0000000..330a4f6 Binary files /dev/null and b/AccompanyingAssistant/shell/surface9999.png differ diff --git a/AudioVisualizer.sln b/AudioVisualizer.sln index 929943a..f3a5800 100644 --- a/AudioVisualizer.sln +++ b/AudioVisualizer.sln @@ -25,78 +25,126 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioWallpaperManager", "Au EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioWallpaper", "AudioVisualizer\AudioWallpaper.csproj", "{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccompanyingAssistant", "AccompanyingAssistant\AccompanyingAssistant.csproj", "{9C088677-7177-4D46-801F-426575401B1B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|Any CPU.Build.0 = Debug|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x64.ActiveCfg = Debug|x64 {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x64.Build.0 = Debug|x64 + {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x86.ActiveCfg = Debug|Any CPU + {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x86.Build.0 = Debug|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|Any CPU.ActiveCfg = Release|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|Any CPU.Build.0 = Release|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x64.ActiveCfg = Release|x64 {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x64.Build.0 = Release|x64 + {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x86.ActiveCfg = Release|Any CPU + {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x86.Build.0 = Release|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|Any CPU.Build.0 = Debug|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x64.ActiveCfg = Debug|x64 {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x64.Build.0 = Debug|x64 + {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x86.ActiveCfg = Debug|Any CPU + {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x86.Build.0 = Debug|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|Any CPU.ActiveCfg = Release|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|Any CPU.Build.0 = Release|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x64.ActiveCfg = Release|x64 {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x64.Build.0 = Release|x64 + {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x86.ActiveCfg = Release|Any CPU + {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x86.Build.0 = Release|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|Any CPU.Build.0 = Debug|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x64.ActiveCfg = Debug|x64 {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x64.Build.0 = Debug|x64 + {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x86.ActiveCfg = Debug|Any CPU + {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x86.Build.0 = Debug|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|Any CPU.ActiveCfg = Release|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|Any CPU.Build.0 = Release|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x64.ActiveCfg = Release|x64 {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x64.Build.0 = Release|x64 + {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x86.ActiveCfg = Release|Any CPU + {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x86.Build.0 = Release|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x64.ActiveCfg = Debug|x64 {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x64.Build.0 = Debug|x64 + {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x86.ActiveCfg = Debug|Any CPU + {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x86.Build.0 = Debug|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|Any CPU.Build.0 = Release|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x64.ActiveCfg = Release|x64 {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x64.Build.0 = Release|x64 + {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x86.ActiveCfg = Release|Any CPU + {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x86.Build.0 = Release|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x64.ActiveCfg = Debug|x64 {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x64.Build.0 = Debug|x64 + {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x86.ActiveCfg = Debug|Any CPU + {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x86.Build.0 = Debug|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|Any CPU.ActiveCfg = Release|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|Any CPU.Build.0 = Release|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x64.ActiveCfg = Release|x64 {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x64.Build.0 = Release|x64 + {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x86.ActiveCfg = Release|Any CPU + {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x86.Build.0 = Release|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x64.ActiveCfg = Debug|x64 {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x64.Build.0 = Debug|x64 + {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x86.ActiveCfg = Debug|Any CPU + {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x86.Build.0 = Debug|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|Any CPU.Build.0 = Release|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x64.ActiveCfg = Release|x64 {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x64.Build.0 = Release|x64 + {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x86.ActiveCfg = Release|Any CPU + {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x86.Build.0 = Release|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x64.ActiveCfg = Debug|x64 {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x64.Build.0 = Debug|x64 + {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x86.ActiveCfg = Debug|Any CPU + {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x86.Build.0 = Debug|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|Any CPU.ActiveCfg = Release|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|Any CPU.Build.0 = Release|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x64.ActiveCfg = Release|x64 {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x64.Build.0 = Release|x64 + {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x86.ActiveCfg = Release|Any CPU + {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x86.Build.0 = Release|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x64.ActiveCfg = Debug|x64 {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x64.Build.0 = Debug|x64 + {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x86.ActiveCfg = Debug|Any CPU + {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x86.Build.0 = Debug|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|Any CPU.Build.0 = Release|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x64.ActiveCfg = Release|x64 {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x64.Build.0 = Release|x64 + {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x86.ActiveCfg = Release|Any CPU + {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x86.Build.0 = Release|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Debug|x64.ActiveCfg = Debug|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Debug|x64.Build.0 = Debug|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Debug|x86.ActiveCfg = Debug|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Debug|x86.Build.0 = Debug|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Release|Any CPU.Build.0 = Release|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Release|x64.ActiveCfg = Release|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Release|x64.Build.0 = Release|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Release|x86.ActiveCfg = Release|Any CPU + {9C088677-7177-4D46-801F-426575401B1B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AudioVisualizer/AccAssManage.cs b/AudioVisualizer/AccAssManage.cs new file mode 100644 index 0000000..00e5e5e --- /dev/null +++ b/AudioVisualizer/AccAssManage.cs @@ -0,0 +1,53 @@ +using AudioWallpaper.ActivityWatch; +using AudioWallpaper.SSO; +using AudioWallpaper.Tools; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper { + public class AccAssManage { + private SentenceHelper SentenceHelper; + + public static AccAssManage? Instance = null; + + public AccAssManage() { + SentenceHelper = new SentenceHelper(); + + SentenceHelper.SentenceChanged += SentenceHelper_SentenceChanged; + SentenceHelper.Start(); + + Instance = this; + } + + public void UpdateLoginState(LoginMeta loginMeta) { + SentenceHelper.UpdateLoginState(loginMeta); + } + public void UpdateUseState(bool isUse) { + SentenceHelper.UpdateUseState(isUse); + } + public void SetInterval(int newInterval) { + SentenceHelper.SetInterval(newInterval); + } + public void Stop() { + SentenceHelper.Stop(); + } + public void Dispose() { + SentenceHelper.SentenceChanged -= SentenceHelper_SentenceChanged; + SentenceHelper.Dispose(); + } + + /// + /// 句子数据更改时执行 + /// + /// + /// + /// + private void SentenceHelper_SentenceChanged(object? sender, FocusStatusRecord fsr) { + Console.WriteLine("假设这是执行了事件的代码"); + Console.WriteLine("这是从事件回调中获取到的数据:" + fsr.Message); + } + } +} diff --git a/AudioVisualizer/ActivityWatch/ActivityAnalyzer.cs b/AudioVisualizer/ActivityWatch/ActivityAnalyzer.cs new file mode 100644 index 0000000..5c83ba1 --- /dev/null +++ b/AudioVisualizer/ActivityWatch/ActivityAnalyzer.cs @@ -0,0 +1,497 @@ +using AudioWallpaper.ActivityWatch; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace AudioWallpaper.ActivityWatch { + internal class ActivityAnalyzer { + private readonly ActivityAnalyzerConfig _config; + private readonly TimeZoneInfo _timeZone; + + public ActivityAnalyzer(ActivityAnalyzerConfig config = null) { + _config = config ?? new ActivityAnalyzerConfig(); + + // 初始化时区 + try { + _timeZone = TimeZoneInfo.FindSystemTimeZoneById(GetWindowsTimeZoneId(_config.Timezone)); + } catch (TimeZoneNotFoundException) { + Console.WriteLine($"警告:未知时区 '{_config.Timezone}',使用默认时区 'China Standard Time'"); + _timeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"); + } + } + + private string GetWindowsTimeZoneId(string timezone) { + // 映射一些常见的时区名称到Windows时区ID + var timeZoneMap = new Dictionary + { + { "Asia/Shanghai", "China Standard Time" }, + { "UTC", "UTC" }, + { "US/Eastern", "Eastern Standard Time" }, + { "US/Pacific", "Pacific Standard Time" }, + { "Europe/London", "GMT Standard Time" }, + { "Europe/Paris", "W. Europe Standard Time" } + }; + + return timeZoneMap.ContainsKey(timezone) ? timeZoneMap[timezone] : timezone; + } + + private DateTime? ParseTimestamp(string timestampStr) { + if (string.IsNullOrEmpty(timestampStr)) + return null; + + try { + // 方法1:直接解析ISO格式 + if (DateTime.TryParse(timestampStr, out DateTime dt)) { + // 转换到指定时区 + if (dt.Kind == DateTimeKind.Utc) { + return TimeZoneInfo.ConvertTimeFromUtc(dt, _timeZone); + } else if (dt.Kind == DateTimeKind.Unspecified) { + return TimeZoneInfo.ConvertTime(dt, _timeZone); + } + return dt; + } + + // 方法2:手动解析 + string cleanTimestamp = timestampStr.Replace('T', ' '); + + if (cleanTimestamp.Contains('+')) + cleanTimestamp = cleanTimestamp.Split('+')[0]; + else if (cleanTimestamp.Contains('Z')) + cleanTimestamp = cleanTimestamp.Replace("Z", ""); + + // 移除毫秒部分 + cleanTimestamp = Regex.Replace(cleanTimestamp, @"\.\d+", ""); + + if (DateTime.TryParseExact(cleanTimestamp, "yyyy-MM-dd HH:mm:ss", + CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedDt)) { + return TimeZoneInfo.ConvertTime(parsedDt, _timeZone); + } + + return null; + } catch (Exception ex) { + Console.WriteLine($"警告:无法解析时间戳 '{timestampStr}': {ex.Message}"); + return null; + } + } + + /// + /// 解析从ActivityWatchClient获取的原始活动数据 + /// + /// ActivityWatchClient.GetEventsAsync()返回的数据 + /// 解析后的活动条目列表 + public List ParseActivityData(List> rawData) { + var activities = new List(); + + if (rawData == null || !rawData.Any()) + return activities; + + foreach (var entry in rawData) { + var processed = ProcessDictionaryEntry(entry); + if (processed != null) + activities.AddRange(processed); + } + + return activities.OrderBy(a => a.Timestamp).ToList(); + } + + /// + /// 从JSON字符串解析活动数据(兼容原有功能) + /// + /// JSON格式的活动数据 + /// 解析后的活动条目列表 + public List ParseActivityDataFromJson(string jsonString) { + var activities = new List(); + + if (string.IsNullOrEmpty(jsonString)) + return activities; + + try { + // 尝试解析为JSON数组 + var jsonArray = JsonSerializer.Deserialize>(jsonString); + foreach (var entry in jsonArray) { + var processed = ProcessEntry(entry); + if (processed != null) + activities.AddRange(processed); + } + } catch (JsonException) { + // 逐行解析 + foreach (var line in jsonString.Split('\n')) { + var trimmedLine = line.Trim(); + if (string.IsNullOrEmpty(trimmedLine)) + continue; + + try { + var entry = JsonSerializer.Deserialize(trimmedLine); + var processed = ProcessEntry(entry); + if (processed != null) + activities.AddRange(processed); + } catch (JsonException) { + // 忽略无法解析的行 + } + } + } + + return activities.OrderBy(a => a.Timestamp).ToList(); + } + + private List ProcessEntry(JsonElement entry) { + try { + var data = entry.TryGetProperty("data", out var dataElement) ? dataElement : entry; + + if (!data.TryGetProperty("app", out var appElement) || + !data.TryGetProperty("title", out var titleElement)) + return null; + + var app = appElement.GetString(); + var title = titleElement.GetString(); + + if (string.IsNullOrEmpty(app) || string.IsNullOrEmpty(title)) + return null; + + var duration = entry.TryGetProperty("duration", out var durationElement) + ? durationElement.GetDouble() : 0.0; + + if (duration < _config.MinDuration || + _config.ExcludedApps.Contains(app) || + _config.ExcludedTitles.Any(keyword => title.Contains(keyword))) + return null; + + var timestampStr = entry.TryGetProperty("timestamp", out var timestampElement) + ? timestampElement.GetString() : ""; + + var parsedTimestamp = ParseTimestamp(timestampStr); + if (!parsedTimestamp.HasValue) + return null; + + return new List + { + new ActivityEntry + { + Timestamp = parsedTimestamp.Value, + Duration = duration, + App = app, + Title = title, + RawTitle = title + } + }; + } catch (Exception) { + return null; + } + } + + private List ProcessDictionaryEntry(Dictionary entry) { + try { + Dictionary data; + + if (entry.ContainsKey("data")) { + if (entry["data"] is JsonElement je && je.ValueKind == JsonValueKind.Object) { + // ✅ 将 JsonElement 转为 Dictionary + data = JsonSerializer.Deserialize>(je.GetRawText()); + } else if (entry["data"] is Dictionary dict) { + data = dict; + } else { + return null; + } + } else { + data = entry; + } + + if (!data.ContainsKey("app") || !data.ContainsKey("title")) + return null; + + var app = data["app"].ToString(); + var title = data["title"].ToString(); + + if (string.IsNullOrEmpty(app) || string.IsNullOrEmpty(title)) + return null; + + var duration = entry.ContainsKey("duration") ? GetValue(entry["duration"]) : 0.0; + + if (duration < _config.MinDuration || + _config.ExcludedApps.Contains(app) || + _config.ExcludedTitles.Any(keyword => title.Contains(keyword))) + return null; + + var timestampStr = entry.ContainsKey("timestamp") ? entry["timestamp"].ToString() : ""; + var parsedTimestamp = ParseTimestamp(timestampStr); + if (!parsedTimestamp.HasValue) + return null; + + return new List + { + new ActivityEntry + { + Timestamp = parsedTimestamp.Value, + Duration = duration, + App = app, + Title = title, + RawTitle = title + } + }; + } catch (Exception e) { + return null; + } + } + private T GetValue(object value, T defaultValue = default) { + try { + if (value == null || value is DBNull) + return defaultValue; + + // 处理 JsonElement 特殊情况 + if (value is JsonElement je) { + if (typeof(T) == typeof(string)) return (T)(object)je.ToString(); + if (typeof(T) == typeof(int) && je.TryGetInt32(out var i)) return (T)(object)i; + if (typeof(T) == typeof(long) && je.TryGetInt64(out var l)) return (T)(object)l; + if (typeof(T) == typeof(double) && je.TryGetDouble(out var d)) return (T)(object)d; + if (typeof(T) == typeof(float) && je.TryGetSingle(out var f)) return (T)(object)f; + if (typeof(T) == typeof(DateTime) && je.TryGetDateTime(out var dt)) return (T)(object)dt; + + // fallback: 尝试反序列化整个对象 + return JsonSerializer.Deserialize(je.GetRawText()); + } + + // 如果已经是目标类型,直接返回 + if (value is T tVal) + return tVal; + + // 尝试 Convert.ChangeType + return (T)Convert.ChangeType(value, typeof(T)); + } catch { + return defaultValue; + } + } + + private List SplitActivityAcrossSlices(ActivityEntry activity, int sliceDurationMinutes) { + var startTime = activity.Timestamp; + var duration = activity.Duration; + var endTime = startTime.AddSeconds(duration); + + var sliceInterval = TimeSpan.FromMinutes(sliceDurationMinutes); + + // 计算开始时间片的边界 + var startSlice = new DateTime(startTime.Year, startTime.Month, startTime.Day, + startTime.Hour, (startTime.Minute / sliceDurationMinutes) * sliceDurationMinutes, 0); + + // 如果活动完全在一个时间片内,直接返回 + var nextSliceBoundary = startSlice.Add(sliceInterval); + if (endTime <= nextSliceBoundary) + return new List { activity }; + + // 将活动分割到多个时间片 + var splitActivities = new List(); + var currentSliceStart = startSlice; + var remainingDuration = duration; + + while (remainingDuration > 0 && currentSliceStart < endTime) { + var currentSliceEnd = currentSliceStart.Add(sliceInterval); + + // 计算当前时间片中的活动开始时间 + var activityStartInSlice = startTime > currentSliceStart ? startTime : currentSliceStart; + var activityEndInSlice = endTime < currentSliceEnd ? endTime : currentSliceEnd; + + // 计算在当前时间片中的持续时间 + var durationInSlice = (activityEndInSlice - activityStartInSlice).TotalSeconds; + + if (durationInSlice > 0) { + splitActivities.Add(new ActivityEntry { + Timestamp = activityStartInSlice, + Duration = durationInSlice, + App = activity.App, + Title = activity.Title, + RawTitle = activity.RawTitle + }); + } + + currentSliceStart = currentSliceEnd; + remainingDuration -= durationInSlice; + } + + return splitActivities; + } + + /// + /// 分析活动数据并生成时间片结果 + /// + /// 活动条目列表 + /// 时间片分析结果 + public List AnalyzeTimeSlices(List activities) { + if (!activities.Any()) + return new List(); + + var startTime = activities.First().Timestamp; + var endTime = activities.Last().Timestamp; + var interval = _config.TimeSliceMinutes; + + var sliceDict = new Dictionary(); + + // 分配活动到时间片 + foreach (var activity in activities) { + var splitActivities = SplitActivityAcrossSlices(activity, interval); + + foreach (var splitActivity in splitActivities) { + var sliceKey = new DateTime(splitActivity.Timestamp.Year, splitActivity.Timestamp.Month, + splitActivity.Timestamp.Day, splitActivity.Timestamp.Hour, + (splitActivity.Timestamp.Minute / interval) * interval, 0); + + if (!sliceDict.ContainsKey(sliceKey)) + sliceDict[sliceKey] = new TimeSliceData(); + + var sliceData = sliceDict[sliceKey]; + sliceData.ActiveSeconds += splitActivity.Duration; + + if (!sliceData.AppUsage.ContainsKey(splitActivity.App)) + sliceData.AppUsage[splitActivity.App] = 0; + sliceData.AppUsage[splitActivity.App] += splitActivity.Duration; + + sliceData.WindowTitles.Add(splitActivity.RawTitle); + sliceData.Events.Add(splitActivity); + + // 关键词分析 + var titleLower = splitActivity.Title.ToLower(); + if (_config.WorkKeywords.Any(kw => titleLower.Contains(kw.ToLower()))) + sliceData.KeywordsMatched++; + if (_config.DistractionKeywords.Any(kw => titleLower.Contains(kw.ToLower()))) + sliceData.DistractionCount++; + } + } + + // 计算切换次数和主导应用 + foreach (var kvp in sliceDict) { + var data = kvp.Value; + + // 计算应用切换次数 + if (data.Events.Count > 1) { + var prevApp = data.Events[0].App; + for (int i = 1; i < data.Events.Count; i++) { + if (data.Events[i].App != prevApp) + data.SwitchCount++; + prevApp = data.Events[i].App; + } + } + + // 确定主导应用 + if (data.AppUsage.Any()) { + data.DominantProcess = data.AppUsage.OrderByDescending(x => x.Value).First().Key; + } + } + + // 构建最终输出 + var results = new List(); + var current = new DateTime(startTime.Year, startTime.Month, startTime.Day, + startTime.Hour, (startTime.Minute / interval) * interval, 0); + + while (current <= endTime) { + if (sliceDict.ContainsKey(current)) { + var data = sliceDict[current]; + + // 计算专注分数 + var maxSeconds = interval * 60; + var timeUtilization = Math.Min(1.0, data.ActiveSeconds / maxSeconds); + var switchPenalty = Math.Min(1.0, data.SwitchCount / 10.0); + var keywordBoost = Math.Min(0.3, data.KeywordsMatched * 0.1); + var distractionPenalty = Math.Min(0.5, data.DistractionCount * 0.2); + + var focusScore = (timeUtilization * 0.7 + keywordBoost) * (1 - switchPenalty) - distractionPenalty; + focusScore = Math.Max(0.0, Math.Min(1.0, focusScore)); + + // 根据阈值确定专注状态 + var focusState = "无活动"; + if (data.ActiveSeconds > 0) { + var sortedThresholds = _config.FocusThresholds.OrderByDescending(x => x.Value); + foreach (var threshold in sortedThresholds) { + if (focusScore >= threshold.Value) { + focusState = threshold.Key; + break; + } + } + } + + var result = new TimeSliceResult { + TimeSlice = current.ToString("yyyy-MM-dd HH:mm"), + Timezone = _config.Timezone, + DominantProcess = data.DominantProcess ?? "none", + ActiveSeconds = Math.Round(data.ActiveSeconds, 1), + SwitchCount = data.SwitchCount, + KeywordsMatched = data.KeywordsMatched, + DistractionEvents = data.DistractionCount, + WindowTitles = data.WindowTitles.ToList(), + FocusScore = Math.Round(focusScore, 2), + FocusState = focusState, + ProductivityScore = Math.Round(focusScore * timeUtilization * 100, 1) + }; + + if (_config.OutputVerbose) { + result.AppUtilization = data.AppUsage.ToDictionary( + kvp => kvp.Key, + kvp => Math.Round(kvp.Value, 1) + ); + } + + results.Add(result); + } else { + // 无活动的空时间片 + results.Add(new TimeSliceResult { + TimeSlice = current.ToString("yyyy-MM-dd HH:mm"), + Timezone = _config.Timezone, + DominantProcess = "none", + ActiveSeconds = 0, + SwitchCount = 0, + KeywordsMatched = 0, + DistractionEvents = 0, + WindowTitles = new List(), + FocusScore = 0.0, + FocusState = "无活动", + ProductivityScore = 0.0 + }); + } + + current = current.AddMinutes(interval); + } + + return results; + } + + /// + /// 完整的分析流程:从ActivityWatchClient数据到时间片结果 + /// + /// ActivityWatchClient.GetEventsAsync()返回的数据 + /// 时间片分析结果 + public List AnalyzeFromRawData(List> rawData) { + var activities = ParseActivityData(rawData); + return AnalyzeTimeSlices(activities); + } + + /// + /// 将结果保存为JSON文件 + /// + /// 分析结果 + /// 输出文件路径 + public async Task SaveResultsToFileAsync(List results, string filePath) { + var options = new JsonSerializerOptions { + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + var jsonString = JsonSerializer.Serialize(results, options); + await File.WriteAllTextAsync(filePath, jsonString); + } + + /// + /// 从配置文件加载配置 + /// + /// 配置文件路径 + /// 配置对象 + public static async Task LoadConfigFromFileAsync(string configFilePath) { + try { + var configJson = await File.ReadAllTextAsync(configFilePath); + return JsonSerializer.Deserialize(configJson); + } catch (Exception) { + return new ActivityAnalyzerConfig(); + } + } + } +} diff --git a/AudioVisualizer/ActivityWatch/ActivityAnalyzerConfig.cs b/AudioVisualizer/ActivityWatch/ActivityAnalyzerConfig.cs new file mode 100644 index 0000000..f1757f4 --- /dev/null +++ b/AudioVisualizer/ActivityWatch/ActivityAnalyzerConfig.cs @@ -0,0 +1,32 @@ +namespace AudioWallpaper.ActivityWatch { + public class ActivityAnalyzerConfig { + public int TimeSliceMinutes { get; set; } = 5; + public double MinDuration { get; set; } = 0.1; + public string Timezone { get; set; } = "Asia/Shanghai"; + public List WorkKeywords { get; set; } = new List + { + "work", "code", "develop", "programming", "research", + "writing", "study", "test", "开发", "编程", "测试", "脚本", "分析", "pycharm" + }; + public List DistractionKeywords { get; set; } = new List + { + "social", "media", "video", "game", "shopping", + "news", "娱乐", "视频", "购物", "游戏", "聊天", "微博" + }; + public List ExcludedApps { get; set; } = new List + { + "explorer.exe", "StartMenuExperienceHost.exe", "SearchApp.exe" + }; + public List ExcludedTitles { get; set; } = new List + { + "任务切换", "任务管理器" + }; + public bool OutputVerbose { get; set; } = true; + public Dictionary FocusThresholds { get; set; } = new Dictionary + { + { "高专注", 0.75 }, + { "中专注", 0.5 }, + { "低专注", 0.0 } + }; + } +} diff --git a/AudioVisualizer/ActivityWatch/ActivityEntry.cs b/AudioVisualizer/ActivityWatch/ActivityEntry.cs new file mode 100644 index 0000000..6fbb5fe --- /dev/null +++ b/AudioVisualizer/ActivityWatch/ActivityEntry.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.ActivityWatch { + public class ActivityEntry { + public DateTime Timestamp { get; set; } + public double Duration { get; set; } + public string App { get; set; } + public string Title { get; set; } + public string RawTitle { get; set; } + } +} diff --git a/AudioVisualizer/ActivityWatch/ActivityWatchClient.cs b/AudioVisualizer/ActivityWatch/ActivityWatchClient.cs new file mode 100644 index 0000000..6f5c655 --- /dev/null +++ b/AudioVisualizer/ActivityWatch/ActivityWatchClient.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Web; + +namespace AudioWallpaper.ActivityWatch { + public class ActivityWatchClient { + private readonly HttpClient _httpClient; + private readonly string _baseUrl; + private readonly string _bucket; + + public ActivityWatchClient(string bucketName, string host = "http://localhost:5600") { + _httpClient = new HttpClient(); + _baseUrl = $"{host}/api/0"; + _bucket = bucketName; + } + + public async Task>> GetEventsAsync(string start, string end) { + var url = $"{_baseUrl}/buckets/{_bucket}/events"; + var uriBuilder = new UriBuilder(url); + var query = HttpUtility.ParseQueryString(uriBuilder.Query); + query["start"] = start; + query["end"] = end; + uriBuilder.Query = query.ToString(); + + var response = await _httpClient.GetAsync(uriBuilder.ToString()); + response.EnsureSuccessStatusCode(); + + var jsonString = await response.Content.ReadAsStringAsync(); + var events = JsonSerializer.Deserialize>>(jsonString); + return events; + } + + public static Dictionary> GenerateTimeRanges(DateTime? now = null) { + var currentTime = now ?? DateTime.UtcNow; + + return new Dictionary> { + ["last_5_minutes"] = new Dictionary { + ["start"] = currentTime.AddMinutes(-5).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), + ["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + }, + ["last_30_minutes"] = new Dictionary { + ["start"] = currentTime.AddMinutes(-30).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), + ["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + }, + ["last_6_hours"] = new Dictionary { + ["start"] = currentTime.AddHours(-6).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), + ["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + }, + ["last_24_hours"] = new Dictionary { + ["start"] = currentTime.AddHours(-24).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), + ["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + }, + ["last_3_days"] = new Dictionary { + ["start"] = currentTime.AddDays(-3).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), + ["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + } + }; + } + + public void Dispose() { + _httpClient?.Dispose(); + } + } +} diff --git a/AudioVisualizer/ActivityWatch/FocusStatusResponseParser.cs b/AudioVisualizer/ActivityWatch/FocusStatusResponseParser.cs new file mode 100644 index 0000000..0b3ef9b --- /dev/null +++ b/AudioVisualizer/ActivityWatch/FocusStatusResponseParser.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AudioWallpaper.ActivityWatch { + + // 定义数据模型 + public class ApiResponse { + [JsonPropertyName("code")] + public int Code { get; set; } + + [JsonPropertyName("data")] + public DataItem[] Data { get; set; } + } + + public class DataItem { + [JsonPropertyName("message")] + public MessageContent Message { get; set; } + } + + public class MessageContent { + [JsonPropertyName("content")] + public string Content { get; set; } + } + + public class FocusStatusRecord { + [JsonPropertyName("time_slice")] + public string TimeSlice { get; set; } + + [JsonPropertyName("focus_state")] + public string FocusState { get; set; } + + [JsonPropertyName("productivity_score")] + public double ProductivityScore { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } + } + + public class FocusStatusParser { + public FocusStatusRecord GetLatestRecord(string jsonResponse) { + // 解析外层响应 + var apiResponse = JsonSerializer.Deserialize(jsonResponse); + + if (apiResponse?.Data == null || apiResponse.Data.Length == 0) + throw new InvalidOperationException("无效的API响应数据"); + + // 提取内容字符串(包含Markdown代码块) + string content = apiResponse.Data[0].Message.Content; + + // 去除Markdown代码块标记 + string cleanJson = content + .Replace("```json", "") + .Replace("```", "") + .Trim(); + + // 解析内部JSON数组 + var records = JsonSerializer.Deserialize(cleanJson); + + if (records == null || records.Length == 0) + throw new InvalidOperationException("未找到专注状态记录"); + + // 返回最新的记录(数组的第一个元素) + return records[0]; + } + } +} diff --git a/AudioVisualizer/ActivityWatch/TimeSliceData.cs b/AudioVisualizer/ActivityWatch/TimeSliceData.cs new file mode 100644 index 0000000..b3484c5 --- /dev/null +++ b/AudioVisualizer/ActivityWatch/TimeSliceData.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.ActivityWatch { + public class TimeSliceData { + public double ActiveSeconds { get; set; } + public Dictionary AppUsage { get; set; } = new Dictionary(); + public HashSet WindowTitles { get; set; } = new HashSet(); + public List Events { get; set; } = new List(); + public int SwitchCount { get; set; } + public int KeywordsMatched { get; set; } + public int DistractionCount { get; set; } + public string DominantProcess { get; set; } + } +} diff --git a/AudioVisualizer/ActivityWatch/TimeSliceResult.cs b/AudioVisualizer/ActivityWatch/TimeSliceResult.cs new file mode 100644 index 0000000..8e5442c --- /dev/null +++ b/AudioVisualizer/ActivityWatch/TimeSliceResult.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.ActivityWatch { + public class TimeSliceResult { + public string TimeSlice { get; set; } + public string Timezone { get; set; } + public string DominantProcess { get; set; } + public double ActiveSeconds { get; set; } + public int SwitchCount { get; set; } + public int KeywordsMatched { get; set; } + public int DistractionEvents { get; set; } + public List WindowTitles { get; set; } + public double FocusScore { get; set; } + public string FocusState { get; set; } + public double ProductivityScore { get; set; } + public Dictionary AppUtilization { get; set; } + +#if DEBUG + + public override string ToString() { + var windowTitles = WindowTitles != null ? string.Join(", ", WindowTitles) : "null"; + var appUtilization = AppUtilization != null + ? string.Join(", ", AppUtilization.Select(kv => $"{kv.Key}:{kv.Value:F2}")) + : "null"; + + return $"TimeSlice: {TimeSlice}, " + + $"Timezone: {Timezone}, " + + $"DominantProcess: {DominantProcess}, " + + $"ActiveSeconds: {ActiveSeconds:F2}, " + + $"SwitchCount: {SwitchCount}, " + + $"KeywordsMatched: {KeywordsMatched}, " + + $"DistractionEvents: {DistractionEvents}, " + + $"WindowTitles: [{windowTitles}], " + + $"FocusScore: {FocusScore:F2}, " + + $"FocusState: {FocusState}, " + + $"ProductivityScore: {ProductivityScore:F2}, " + + $"AppUtilization: [{appUtilization}]"; + } + } +#endif +} diff --git a/AudioVisualizer/AudioWallpaper.csproj b/AudioVisualizer/AudioWallpaper.csproj index 34aeb9e..6f1b011 100644 --- a/AudioVisualizer/AudioWallpaper.csproj +++ b/AudioVisualizer/AudioWallpaper.csproj @@ -1,5 +1,9 @@  + + False + + WinExe net8.0-windows10.0.17763.0 @@ -19,18 +23,42 @@ 10.0.17763.0 + + + + + + + + + + + + aximp + 1 + 1 + eab22ac0-30c1-11cf-a7eb-0000c05bae0b + 0 + false + + + + + + + @@ -49,4 +77,8 @@ + + + + \ No newline at end of file diff --git a/AudioVisualizer/Entity/ConfigurationObject.cs b/AudioVisualizer/Entity/ConfigurationObject.cs index fec42d3..4d4780d 100644 --- a/AudioVisualizer/Entity/ConfigurationObject.cs +++ b/AudioVisualizer/Entity/ConfigurationObject.cs @@ -3,6 +3,7 @@ public class ConfigurationObject { private GeneralConfigurationObjects? generalConfigurationObjects; private VideoWallpaperConfigObject? videoWallpaperConfigObject; + private OtherConfigObjects? otherConfigObjects; public bool DeviceStateChange = false; public bool SignRenderingStatus = false; public bool RenderingStatus = true; @@ -38,6 +39,21 @@ } } } + public OtherConfigObjects OtherConfigObjects { + get { + if (otherConfigObjects != null) { + return otherConfigObjects; + } + return new OtherConfigObjects(); + } + set { + if (value != null) { + otherConfigObjects = value; + } else { + otherConfigObjects = new OtherConfigObjects(); + } + } + } } } diff --git a/AudioVisualizer/Entity/OtherConfigObjects.cs b/AudioVisualizer/Entity/OtherConfigObjects.cs new file mode 100644 index 0000000..4bf29ee --- /dev/null +++ b/AudioVisualizer/Entity/OtherConfigObjects.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.Entity { + [Serializable] + public class OtherConfigObjects { + /// + /// 配置名称 + /// + private const string CONFIG_NAME = "OtherConfig"; + + /// + /// 是否使用陪伴助手 + /// + public bool UseAccompanyingAssistant = false; + + /// + /// Bucket名称 + /// + public string? BucketName = null; + /// + /// 数据获取范围时间 + /// + public string? RangeTime = null; + /// + /// 数据获取间隔时间,单位秒,默认五分钟 + /// + public int Interval = 60 * 5; + + public string DisplayName = "Unknown"; + + /// + /// 该配置是否为全局配置 + /// + private const bool OVERALL_SITUATION = true; + + + public bool SaveConfig(string configFilePath) { + ConfigurationTools configurationTools = new ConfigurationTools(configFilePath); + + string fullConfigName = OVERALL_SITUATION ? CONFIG_NAME : DisplayName + "_" + CONFIG_NAME; + + configurationTools.AddSetting(fullConfigName, "UseAccompanyingAssistant", UseAccompanyingAssistant.ToString()); + configurationTools.AddSetting(fullConfigName, "BucketName", BucketName ?? string.Empty); + configurationTools.AddSetting(fullConfigName, "RangeTime", RangeTime ?? string.Empty); + configurationTools.AddSetting(fullConfigName, "Interval", Interval.ToString()); + + configurationTools.SaveSettings(); + return true; + } + public OtherConfigObjects LoadConfig(string configFilePath, string name) { + try { + ConfigurationTools configurationTools = new ConfigurationTools(configFilePath); + string fullConfigName = OVERALL_SITUATION ? CONFIG_NAME : name + "_" + CONFIG_NAME; + UseAccompanyingAssistant = Convert.ToBoolean(configurationTools.GetSetting(fullConfigName, "UseAccompanyingAssistant")); + BucketName = configurationTools.GetSetting(fullConfigName, "BucketName"); + RangeTime = configurationTools.GetSetting(fullConfigName, "RangeTime"); + Interval = Convert.ToInt32(configurationTools.GetSetting(fullConfigName, "Interval")); + return this; + } catch (Exception) { + return this; + } + } + } +} diff --git a/AudioVisualizer/Entity/UIModification.cs b/AudioVisualizer/Entity/UIModification.cs new file mode 100644 index 0000000..21ac219 --- /dev/null +++ b/AudioVisualizer/Entity/UIModification.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.Entity { + public class UIModification { + public string? ControlName { get; set; } + public string? PropertyName { get; set; } + public object? Value { get; set; } + } + +} diff --git a/AudioVisualizer/Fakes/Microsoft.Web.WebView2.Core.Projection.fakes b/AudioVisualizer/Fakes/Microsoft.Web.WebView2.Core.Projection.fakes new file mode 100644 index 0000000..6f5ac9d Binary files /dev/null and b/AudioVisualizer/Fakes/Microsoft.Web.WebView2.Core.Projection.fakes differ diff --git a/AudioVisualizer/Fakes/Microsoft.Web.WebView2.Core.Projection01.fakes b/AudioVisualizer/Fakes/Microsoft.Web.WebView2.Core.Projection01.fakes new file mode 100644 index 0000000..7f9700a Binary files /dev/null and b/AudioVisualizer/Fakes/Microsoft.Web.WebView2.Core.Projection01.fakes differ diff --git a/AudioVisualizer/FormManager.cs b/AudioVisualizer/FormManager.cs index 6abf566..1f2385a 100644 --- a/AudioVisualizer/FormManager.cs +++ b/AudioVisualizer/FormManager.cs @@ -62,7 +62,7 @@ namespace AudioWallpaper { } return; } - + Console.WriteLine("重新加载配置"); GeneralConfigurationObjects generalConfigurationObjects = configurationObject.GeneralConfigurationObjects; MainWindowReLoadConfig(generalConfigurationObjects); @@ -141,7 +141,7 @@ namespace AudioWallpaper { } public void LoadWindowSet() { //加载窗体设置 - //Console.WriteLine("窗体管理器正在初始化"); + Console.WriteLine("窗体管理器正在初始化"); ReCreateWindow(); } diff --git a/AudioVisualizer/MainWindow.cs b/AudioVisualizer/MainWindow.cs index 98286ca..672ec6e 100644 --- a/AudioVisualizer/MainWindow.cs +++ b/AudioVisualizer/MainWindow.cs @@ -1,4 +1,6 @@ +using AudioWallpaper.ActivityWatch; using AudioWallpaper.Entity; +using AudioWallpaper.Tools; using LibAudioVisualizer; using NAudio.CoreAudioApi; using NAudio.Wave; @@ -26,6 +28,8 @@ namespace AudioWallpaper { public delegate void FullScreenDetected(bool status); public event FullScreenDetected FullScreenDetectedEvent; + private string DrawText = null; + public MainWindow(GeneralConfigurationObjects configuration) { appBarManager = new AppBarManager(Handle); generalConfigurationObjects = configuration; @@ -43,6 +47,8 @@ namespace AudioWallpaper { Init(); Win32.SetParent(this.Handle, programIntPtr);//ᴥDisplayChange¼еѧ } + + /// /// ȡɫ /// @@ -122,11 +128,11 @@ namespace AudioWallpaper { public void Capture_DataAvailable(object? sender, WaveInEventArgs e) { int length = e.BytesRecorded / 4; // (ÿһ 4 ֽ) double[] result = new double[length]; // - + for (int i = 0; i < length; i++) { - result[i] = BitConverter.ToSingle(e.Buffer, i * 4)*generalConfigurationObjects.DefaultRadical; // ȡֵ - + result[i] = BitConverter.ToSingle(e.Buffer, i * 4) * generalConfigurationObjects.DefaultRadical; // ȡֵ + //result[i] = db; } @@ -460,6 +466,68 @@ namespace AudioWallpaper { g.FillRectangle(leftB, left); g.FillRectangle(rightB, right); } + /// + /// һıָλ + /// + /// + /// ı + /// x + /// y + /// Ƿֱ + /// Զ + /// ʽ + /// + /// С + /// ɫ + private void DrawingText(Graphics g, string text, int x, int y, + bool isVertical = false, bool wordWrap = false, + FontStyle style = FontStyle.Bold, + string fontName = "Arial", float fontSize = 16, + Color? textColor = null) { + if (string.IsNullOrEmpty(text)) text = string.Empty; + textColor ??= Color.White; // Ĭϰɫı + + using Font font = new Font(fontName, fontSize, style); + using Brush brush = new SolidBrush(textColor.Value); + + // ı + if (!isVertical) { + if (wordWrap) { + // Զģʽʹþ + TextFormatFlags flags = TextFormatFlags.WordBreak; + TextRenderer.DrawText(g, text, font, new Rectangle(x, y, 600, 600), textColor.Value, flags); + } else { + // ģʽ + g.DrawString(text, font, brush, x, y); + } + } + // ı + else { + float charHeight = font.Height; + float charSpacing = charHeight * 0.2f; // ַ + float currentY = y; + float maxWidth = 0; + + // Ԥַ + foreach (char c in text) { + float charWidth = g.MeasureString(c.ToString(), font).Width; + if (charWidth > maxWidth) maxWidth = charWidth; + } + + // ַ + foreach (char c in text) { + // Զд + if (wordWrap && currentY > g.VisibleClipBounds.Bottom - charHeight) { + x += (int)maxWidth + 2; // + currentY = y; + } + + // Ƶַ + g.DrawString(c.ToString(), font, brush, x, currentY); + currentY += charHeight + charSpacing; + } + } + } int colorIndex = 0; double rotation = 0; @@ -508,6 +576,7 @@ namespace AudioWallpaper { if (generalConfigurationObjects.WavyLine) { DrawCurve(g, pen, visualizer.SampleData, visualizer.SampleData.Length, drawingPanel.Width, 0, drawingPanel.Height / 2, MathF.Min(drawingPanel.Height / 10, 100)); } + DrawingText(g, DrawText, 700, 50, false, true); buffer.Render(); oldBuffer = buffer; // һ buffer (֮Բȫֻʹһ Buffer Ϊ,,, ûܵڴС, ÿһ֡Ӧ) } diff --git a/AudioVisualizer/OtherWallpaper/ExecutableWallpaper.cs b/AudioVisualizer/OtherWallpaper/ExecutableWallpaper.cs new file mode 100644 index 0000000..b3cb0f9 --- /dev/null +++ b/AudioVisualizer/OtherWallpaper/ExecutableWallpaper.cs @@ -0,0 +1,101 @@ +using AudioWallpaper.Tools; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.OtherWallpaper { + public class ExecutableWallpaper { + public IntPtr Handle = IntPtr.Zero; + public IntPtr MainWindowHandle = IntPtr.Zero; + public String Url; + public Process WallpaperProcess; + private nint programIntPtr; + + public ExecutableWallpaper(String url) { + this.Url = url; + } + public Process Start() { + WallpaperProcess = new Process(); + WallpaperProcess.StartInfo = new ProcessStartInfo(Url); + WallpaperProcess.Start(); + return WallpaperProcess; + } + public void ReShow(int x, int y, int w, int h) { + + } + public IntPtr GetHandle() { + if (Handle == IntPtr.Zero) { + //获取Handle + if (WallpaperProcess == null) { + throw new NullReferenceException("WallpaperProcess Is Null Or Close"); + } + Handle = WallpaperProcess.Handle; + Console.WriteLine(Handle); + } else { + return Handle; + } + return MainWindowHandle; + } + public IntPtr GetMainWindowHandle() { + if (MainWindowHandle == IntPtr.Zero) { + if (WallpaperProcess == null || WallpaperProcess.HasExited) { + throw new NullReferenceException("WallpaperProcess Is Null Or Close"); + } + MainWindowHandle = WallpaperProcess.MainWindowHandle; + } else { + return MainWindowHandle; + } + Console.WriteLine(MainWindowHandle); + return MainWindowHandle; + } + public void SetShowPatternWallpaper() { + GetMainWindowHandle(); + GetHandle(); + Console.WriteLine("请输入句柄"); + int temph = int.Parse(Console.ReadLine()); + Console.WriteLine(".........."); + Console.WriteLine(temph); + // 通过类名查找一个窗口,返回窗口句柄。 + programIntPtr = Win32.FindWindow("Progman", null); + + // 窗口句柄有效 + if (programIntPtr != IntPtr.Zero) { + + IntPtr result = IntPtr.Zero; + + // 向 Program Manager 窗口发送 0x52c 的一个消息,超时设置为0x3e8(1秒)。 + Win32.SendMessageTimeout(programIntPtr, 0x52c, IntPtr.Zero, IntPtr.Zero, 0, 0x3e8, result); + + // 遍历顶级窗口 + Win32.EnumWindows((hwnd, lParam) => { + // 找到包含 SHELLDLL_DefView 这个窗口句柄的 WorkerW + if (Win32.FindWindowEx(hwnd, IntPtr.Zero, "SHELLDLL_DefView", null) != IntPtr.Zero) { + // 找到当前 WorkerW 窗口的,后一个 WorkerW 窗口。 + IntPtr tempHwnd = Win32.FindWindowEx(IntPtr.Zero, hwnd, "WorkerW", null); + // 隐藏这个窗口 + Win32.ShowWindow(tempHwnd, 0); + } + return true; + }, IntPtr.Zero); + } + Win32.SetParent(temph, programIntPtr);//这里会触发DisplayChange事件,有点玄学 + Win32.SetWindowPos(temph, IntPtr.Zero, 1920, 0, 1920, 1080, WindowPositionConstants.SWP_SHOWWINDOW); + } + /// + /// 结束壁纸 + /// + /// true 结束成功 flase 结束失败 + public bool Close() { + if (WallpaperProcess != null && !WallpaperProcess.HasExited) { + WallpaperProcess.Kill(); + } else { + return true; + } + return WallpaperProcess.HasExited; + } + + } +} diff --git a/AudioVisualizer/OtherWallpaper/OtherWallpaperManager.cs b/AudioVisualizer/OtherWallpaper/OtherWallpaperManager.cs new file mode 100644 index 0000000..b416be9 --- /dev/null +++ b/AudioVisualizer/OtherWallpaper/OtherWallpaperManager.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.OtherWallpaper { + public class OtherWallpaperManager { + public List ExecutableWallpapers = new List(); + public OtherWallpaperManager() { + + ExecutableWallpaper executableWallpaper = new ExecutableWallpaper("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"); + executableWallpaper.Start(); + executableWallpaper.SetShowPatternWallpaper(); + } + } +} diff --git a/AudioVisualizer/Program.cs b/AudioVisualizer/Program.cs index 7ea3d64..4470352 100644 --- a/AudioVisualizer/Program.cs +++ b/AudioVisualizer/Program.cs @@ -1,3 +1,7 @@ +using AccompanyingAssistant; +using AudioWallpaper.IPC; +using AudioWallpaper.OtherWallpaper; +using AudioWallpaper.WebWallpaper; using System.Runtime.InteropServices; namespace AudioWallpaper { @@ -15,6 +19,14 @@ namespace AudioWallpaper { public static extern bool AllocConsole(); [DllImport("kernel32.dll")] public static extern bool FreeConsole();//ͷŹĿ̨ + private static SSOManager _ssoManager = new SSOManager(); + //public static SSOManager SSOM { get; private set; }; + public static SSOManager SSOM { + get { + return _ssoManager; + } + } + public static WindowManager WindowManager { get; private set; } = new WindowManager(); [STAThread] static void Main(String[] args) { @@ -24,6 +36,9 @@ namespace AudioWallpaper { bool DoesTheGuardianExist = false; // һΨһĻƣȷΨһ using (Mutex mutex = new Mutex(true, "{C8F8E5D9-6F05-4F92-BE10-DF7A4F5F97FB}", out createdNew)) { +#if DEBUG + createdNew = true; +#endif if (createdNew) { //жǷ //if (args.Length < 1) { @@ -55,9 +70,18 @@ namespace AudioWallpaper { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); ApplicationConfiguration.Initialize(); - + Task.Run(() => SSOM.Initialize()); // Run SSOManager initialization in a separate task // FormManager formManager = new FormManager(); + //ֹ + AccAssManage accAssManage = new AccAssManage(); + + IPCClientExample.RunAsync(); // IPCͻʾ + + + //WebWallpaperManager webWallpaperManager = new WebWallpaperManager("https://blog.ysit.top"); + //OtherWallpaperManager otherWallpaperManager = new OtherWallpaperManager(); + Application.Run(); //ͷſ̨ FreeConsole(); diff --git a/AudioVisualizer/SSO/AuthConfig.cs b/AudioVisualizer/SSO/AuthConfig.cs new file mode 100644 index 0000000..66a5dfc --- /dev/null +++ b/AudioVisualizer/SSO/AuthConfig.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static System.Formats.Asn1.AsnWriter; + +namespace AudioWallpaper.SSO { + public class AuthConfig { + public String clientId; + public Uri redirectUri; + public String scope; + public Uri discoveryUri; + public String authorityUrl = "http://localhost:8080/realms/ysit"; + public String tokenEndpoint = "http://localhost:8080/realms/ysit/protocol/openid-connect/token"; + public String authorizationEndpoint = "http://localhost:8080/realms/ysit/protocol/openid-connect/auth"; + public String revocationEndpoint = "http://localhost:8080/realms/ysit/protocol/openid-connect/revoke"; + public String userInfoEndpoint = "http://localhost:8080/realms/ysit/protocol/openid-connect/userinfo"; + //访问令牌失效后最大刷新次数 + public int maxRefreshCount = 3; + //获取用户信息失败后最大尝试次数 + public int maxGetUserInfoCount = 3; + + public AuthConfig() { + this.clientId = "yiconnect"; + this.redirectUri = new Uri("http://127.0.0.1:5000/"); + this.scope = "openid email profile offline_access"; + this.discoveryUri = new Uri("http://localhost:8080/realms/ysit/.well-known/openid-configuration"); + } + + public AuthConfig(String clientId,Uri redirectUri) { + this.clientId = clientId; + this.redirectUri = redirectUri; + this.scope = "openid email profile offline_access"; + this.discoveryUri = new Uri("http://localhost:8080/realms/ysit/.well-known/openid-configuration"); + } + + } +} diff --git a/AudioVisualizer/SSO/AuthManager.cs b/AudioVisualizer/SSO/AuthManager.cs new file mode 100644 index 0000000..84f87e7 --- /dev/null +++ b/AudioVisualizer/SSO/AuthManager.cs @@ -0,0 +1,280 @@ +using IdentityModel.OidcClient; +using IdentityModel.OidcClient.Results; +using System.Net.Http.Headers; +using System.Text.Json; +using AudioWallpaper.Sso.Exceptions; + +namespace AudioWallpaper.SSO { + public class AuthManager { + private OidcClient _oidcClient; + private readonly AuthConfig authConfig = new(); + public static readonly AuthManager Instance = new AuthManager(); + private AuthStatus authStatus = AuthStatus.Unknown; + + //登录成功回调 + public Action? OnLoginSuccess; + //登录失败回调 + public Action? OnLoginFailed; + //刷新令牌成功回调 + public Action? OnRefreshTokenSuccess; + //刷新令牌失败回调 + public Action? OnRefreshTokenFailed; + //获取用户信息成功回调 + public Action? OnGetUserInfoSuccess; + //获取用户信息失败回调 + public Action? OnGetUserInfoFailed; + //撤销令牌成功回调 + public Action? OnRevokeTokenSuccess; + //撤销令牌失败回调 + public Action? OnRevokeTokenFailed; + //注销登录成功回调 + public Action? OnLogoutSuccess; + //注销登录失败回调 + public Action? OnLogoutFailed; + //登录状态改变回调 + public Action OnAuthStatusChanged; + + public AuthStatus AuthStatus { + get => authStatus; set { + authStatus = value; + OnAuthStatusChanged?.Invoke(value); + } + } + + public AuthManager() { + var options = new OidcClientOptions { + Authority = authConfig.authorityUrl, + ClientId = authConfig.clientId, + RedirectUri = authConfig.redirectUri.ToString(), + Scope = authConfig.scope, + Policy = new Policy { + RequireAccessTokenHash = false, + }, + + LoadProfile = true + }; + + _oidcClient = new OidcClient(options); + Console.WriteLine("OIDC 客户端初始化"); + } + + public async void Login(Form? from = null) { + if (from == null) { + OnLoginFailed?.Invoke("登录窗口未初始化"); + return; + } + if (_oidcClient == null) { + OnLoginFailed?.Invoke("OIDC客户端未初始化"); + return; + } + if (AuthStatus == AuthStatus.Success) { + OnLoginFailed?.Invoke("已登录,请先注销"); + return; + } + + AuthStatus = AuthStatus.InProgress; + _oidcClient.Options.Browser = new WebView2Browser(from); + + try { + + var result = await _oidcClient.LoginAsync(new LoginRequest()); + if (result.IsError) { + AuthStatus = AuthStatus.Failed; + OnLoginFailed?.Invoke(result.Error); + } else { + TokenSet tokenSet = new( + result.AccessToken, + result.RefreshToken, + result.IdentityToken + ); + SsoException? exception = CheckToken(tokenSet); + if (exception != null) { + throw exception; + } + AuthStatus = AuthStatus.Success; + OnLoginSuccess?.Invoke(tokenSet); + + } + } catch (Exception ex) { + AuthStatus = AuthStatus.Failed; + OnLoginFailed?.Invoke("登录异常:" + ex.Message); + } + } + + + /// + /// 获取用户信息 + /// + /// 访问令牌 + /// 用户信息 + public UserInfoSet? GetUserInfo(string? accessToken) { + try { + if (string.IsNullOrWhiteSpace(accessToken)) { + throw new SsoException("AccessToken 为空"); + } + + using var httpClient = new HttpClient(); + var request = new HttpRequestMessage(HttpMethod.Get, authConfig.userInfoEndpoint); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = httpClient.Send(request); + if (!response.IsSuccessStatusCode) { + OnGetUserInfoFailed?.Invoke($"获取用户信息失败,状态码: {response.StatusCode}"); + return null; + } + + var content = response.Content.ReadAsStringAsync().Result; + var userInfoSet = JsonSerializer.Deserialize(content); + if (userInfoSet == null) { + throw new SsoException("获取用户信息失败,内容解析失败"); + } + OnGetUserInfoSuccess?.Invoke(userInfoSet); + return userInfoSet; + } catch (Exception ex) { + OnGetUserInfoFailed?.Invoke($"获取用户信息异常: {ex.Message}"); + return null; + } + } + /// + /// 刷新令牌 + /// + /// 刷新令牌 + /// 令牌信息 + public async Task RefreshToken(string? refreshToken) { + try { + if (_oidcClient == null) { + OnRefreshTokenFailed?.Invoke("OidcClient 未初始化"); + return null; + } + if (string.IsNullOrWhiteSpace(refreshToken)) { + OnRefreshTokenFailed?.Invoke("刷新令牌失败:refreshToken 不能为空"); + return null; + } + // 调用 OidcClient 的刷新令牌方法 + RefreshTokenResult result = await _oidcClient.RefreshTokenAsync(refreshToken); + + if (result.IsError) { + OnRefreshTokenFailed?.Invoke($"刷新令牌失败: {result.Error}"); + return null; + } + var tokenSet = new TokenSet( + result.AccessToken, + result.RefreshToken, + result.IdentityToken + ); + SsoException? exception = CheckToken(tokenSet); + if (exception != null) { + throw exception; + } + AuthStatus = AuthStatus.Success; + OnRefreshTokenSuccess?.Invoke(tokenSet); + return tokenSet; + } catch (Exception ex) { + OnRefreshTokenFailed?.Invoke($"刷新令牌异常: {ex.Message}"); + return null; + } + } + /// + /// 撤销令牌 + /// + /// token + /// token id + public async void RevokeToken(string? token, string? tokenTypeHint, bool form = false) { + if (string.IsNullOrWhiteSpace(token)) { + OnRevokeTokenFailed?.Invoke("撤销令牌失败:token 不能为空"); + if (form) { + OnLogoutFailed?.Invoke("token 不能为空"); + } + return; + } + if (string.IsNullOrWhiteSpace(tokenTypeHint)) { + OnRevokeTokenFailed?.Invoke("撤销令牌失败:tokenTypeHint 不能为空"); + if (form) { + OnLogoutFailed?.Invoke("tokenTypeHint 不能为空"); + } + return; + } + + try { + using var httpClient = new HttpClient(); + var parameters = new Dictionary + { + { "token", token } + }; + if (!string.IsNullOrWhiteSpace(tokenTypeHint)) { + parameters.Add("token_type_hint", tokenTypeHint); + } + parameters.Add("client_id", authConfig.clientId); + + // 如果有 client_secret,说明是 confidential client + var clientSecretProperty = authConfig.GetType().GetProperty("clientSecret"); + if (clientSecretProperty != null) { + var clientSecret = clientSecretProperty.GetValue(authConfig) as string; + if (!string.IsNullOrWhiteSpace(clientSecret)) { + parameters.Add("client_secret", clientSecret); + } + } + + var content = new FormUrlEncodedContent(parameters); + var response = await httpClient.PostAsync(authConfig.revocationEndpoint, content); + var respContent = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) { + // 撤销令牌成功,修改登录状态 + AuthStatus = AuthStatus.LoginOut; + OnRevokeTokenSuccess?.Invoke(); + if (form) { + OnLogoutSuccess?.Invoke(); + } + return; + } else { + OnRevokeTokenFailed?.Invoke($"撤销令牌失败,状态码: {response.StatusCode},内容: {respContent}"); + if (form) { + OnLogoutFailed?.Invoke($"撤销令牌失败,状态码: {response.StatusCode},内容: {respContent}"); + } + return; + } + } catch (Exception ex) { + OnRevokeTokenFailed?.Invoke($"撤销令牌异常: {ex.Message}"); + if (form) { + OnLogoutFailed?.Invoke($"撤销令牌异常: {ex.Message}"); + } + } + } + /// + /// 注销登录 + /// + public void Logout(TokenSet? set) { + SsoException? exception = CheckToken(set); + if (exception != null) { + OnLogoutFailed?.Invoke(exception.Message); + return; + } + +#pragma warning disable CS8602 // 解引用可能出现空引用。 + RevokeToken(set.RefreshToken, set.IdToken, true); +#pragma warning restore CS8602 // 解引用可能出现空引用。 + //TokenManager + } + /// + /// 检查 TokenSet 是否有效 + /// + /// TokenSet 对象 + /// 如果有效返回Null,如果无效返回对应的Exception + public static SsoException? CheckToken(TokenSet? tokenSet) { + if (tokenSet == null) { + return new SsoException("TokenSet 为空"); + } + if (string.IsNullOrWhiteSpace(tokenSet.AccessToken)) { + return new SsoException("AccessToken 为空"); + } + if (string.IsNullOrWhiteSpace(tokenSet.RefreshToken)) { + return new SsoException("RefreshToken 为空"); + } + if (string.IsNullOrWhiteSpace(tokenSet.IdToken)) { + return new SsoException("IdToken 为空"); + } + return null; + } + } +} diff --git a/AudioVisualizer/SSO/AuthStatus.cs b/AudioVisualizer/SSO/AuthStatus.cs new file mode 100644 index 0000000..cde75f3 --- /dev/null +++ b/AudioVisualizer/SSO/AuthStatus.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.SSO { + public enum AuthStatus : int { + Unknown = 0x0000,//未知,一般是没有登录或程序刚启动 + InProgress = 0x0001,//正在进行中,表示正在进行登录操作 + Success = 0x0002,//登录成功 + Cancelled = 0x0003,//用户取消登录 + Failed = 0x0004,//登录失败 + Timeout = 0x0005,//登录超时 + LockedOut = 0x0006,//用户被锁定 + RequiresVerify = 0x0007,//需要验证 + LoginOut = 0x0100,//已经退出登录 + } +} \ No newline at end of file diff --git a/AudioVisualizer/SSO/Exceptions/SsoException.cs b/AudioVisualizer/SSO/Exceptions/SsoException.cs new file mode 100644 index 0000000..3dde219 --- /dev/null +++ b/AudioVisualizer/SSO/Exceptions/SsoException.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.Sso.Exceptions { + public class SsoException:Exception { + public SsoException() { + } + public SsoException(string message) : base(message) { + } + public SsoException(string message, Exception innerException) : base(message, innerException) { + } + public SsoException(string message, string? errorCode) : base(message) { + ErrorCode = errorCode; + } + public string? ErrorCode { get; set; } = null; + } +} diff --git a/AudioVisualizer/SSO/LoginMeta.cs b/AudioVisualizer/SSO/LoginMeta.cs new file mode 100644 index 0000000..19a05ee --- /dev/null +++ b/AudioVisualizer/SSO/LoginMeta.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.SSO { + public class LoginMeta { + public bool IsLoggedIn { get; set; } + public DateTime LastLoginTime { get; set; } = DateTime.Now; + public UserInfoSet? UserInfo { get; set; } + } + +} diff --git a/AudioVisualizer/SSO/LoopbackHttpListener.cs b/AudioVisualizer/SSO/LoopbackHttpListener.cs new file mode 100644 index 0000000..251b4f3 --- /dev/null +++ b/AudioVisualizer/SSO/LoopbackHttpListener.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.SSO { + public class LoopbackHttpListener { + private readonly string _url; + private readonly string _redirectUri; + + public LoopbackHttpListener(int port, string redirectUri) { + _redirectUri = redirectUri; + _url = $"http://127.0.0.1:{port}/"; + } + + public async Task WaitForCallbackAsync() { + using var listener = new HttpListener(); + listener.Prefixes.Add(_url); + listener.Start(); + + var context = await listener.GetContextAsync(); + var request = context.Request; + + var response = context.Response; + response.Headers.Add("Content-Type", "text/html; charset=UTF-8"); + string responseString = "

登录成功!请返回应用程序。

"; + var buffer = Encoding.UTF8.GetBytes(responseString); + response.ContentLength64 = buffer.Length; + response.OutputStream.Write(buffer, 0, buffer.Length); + response.OutputStream.Close(); + + listener.Stop(); + return request.Url.ToString(); + } + } +} diff --git a/AudioVisualizer/SSO/SystemBrowser.cs b/AudioVisualizer/SSO/SystemBrowser.cs new file mode 100644 index 0000000..d2c2636 --- /dev/null +++ b/AudioVisualizer/SSO/SystemBrowser.cs @@ -0,0 +1,44 @@ +using IdentityModel.OidcClient.Browser; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.SSO { + public class SystemBrowser : IBrowser { + private readonly int _port; + + public SystemBrowser(int port) { + _port = port; + } + + public async Task InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default) { + var listener = new LoopbackHttpListener(_port, options.EndUrl); + + Process.Start(new ProcessStartInfo { + FileName = options.StartUrl, + UseShellExecute = true + }); + + try { + var result = await listener.WaitForCallbackAsync(); + return new BrowserResult { + Response = result, + ResultType = BrowserResultType.Success + }; + } catch (TaskCanceledException) { + return new BrowserResult { + ResultType = BrowserResultType.Timeout, + Error = "Login timeout" + }; + } catch (Exception ex) { + return new BrowserResult { + ResultType = BrowserResultType.UnknownError, + Error = ex.Message + }; + } + } + } +} diff --git a/AudioVisualizer/SSO/TokenManager.cs b/AudioVisualizer/SSO/TokenManager.cs new file mode 100644 index 0000000..6a09826 --- /dev/null +++ b/AudioVisualizer/SSO/TokenManager.cs @@ -0,0 +1,160 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; + +namespace AudioWallpaper.SSO { + + public interface ITokenStore { + void Save(string tokenName, string token); + string? Get(string tokenName); + void Clear(string tokenName); + } + public class EncryptedFileTokenStore : ITokenStore { + private readonly string _storagePath; + private readonly object _lock = new(); + + public EncryptedFileTokenStore(string storageDirectory) { + _storagePath = storageDirectory; + Directory.CreateDirectory(_storagePath); + } + + private string GetTokenFilePath(string tokenName) => + Path.Combine(_storagePath, $"{tokenName}.token"); + + public void Save(string tokenName, string token) { + var data = Encoding.UTF8.GetBytes(token); + var encrypted = ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser); + + lock (_lock) { + File.WriteAllBytes(GetTokenFilePath(tokenName), encrypted); + } + } + + public string? Get(string tokenName) { + var path = GetTokenFilePath(tokenName); + if (!File.Exists(path)) + return null; + + try { + lock (_lock) { + var encrypted = File.ReadAllBytes(path); + var decrypted = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.CurrentUser); + return Encoding.UTF8.GetString(decrypted); + } + } catch { + return null; // 可选:记录日志 + } + } + + public void Clear(string tokenName) { + var path = GetTokenFilePath(tokenName); + lock (_lock) { + if (File.Exists(path)) + File.Delete(path); + } + } + } + + + public class TokenManager { + private readonly ITokenStore _store; + + + private const string AccessTokenKey = "access_token"; + private const string RefreshTokenKey = "refresh_token"; + private const string IdTokenKey = "id_token"; + private const string LoginMetaKey = "login_state"; + + public TokenManager(ITokenStore store) { + _store = store ?? throw new ArgumentNullException(nameof(store)); + } + + // 保存 TokenSet + public void SaveToken(TokenSet tokens) { + SaveToken(tokens, null); + } + public void SaveToken(TokenSet tokens, UserInfoSet? userInfoSet) { + if (tokens == null) throw new ArgumentNullException(nameof(tokens)); + + if (string.IsNullOrWhiteSpace(tokens.AccessToken)) + throw new ArgumentException("Access token is required."); + if (string.IsNullOrWhiteSpace(tokens.RefreshToken)) + throw new ArgumentException("Refresh token is required."); + if (string.IsNullOrWhiteSpace(tokens.IdToken)) + throw new ArgumentException("ID token is required."); + + _store.Save(AccessTokenKey, tokens.AccessToken); + _store.Save(RefreshTokenKey, tokens.RefreshToken); + _store.Save(IdTokenKey, tokens.IdToken); + if (userInfoSet != null) { + + LoginMeta loginMeta = new LoginMeta { + IsLoggedIn = true, + UserInfo = userInfoSet + }; + SaveLoginMeta(loginMeta); + } + } + public void SaveLoginMeta(LoginMeta? loginMeta) { + if (loginMeta == null) + throw new ArgumentNullException(nameof(loginMeta)); + + string loginMetaJson = JsonSerializer.Serialize(loginMeta); + _store.Save(LoginMetaKey, loginMetaJson); + + } + + // 保存单个 token + public void SaveToken(string name, string token) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Token name is required."); + if (string.IsNullOrWhiteSpace(token)) throw new ArgumentException("Token value is required."); + + _store.Save(name, token); + } + + // 获取 TokenSet + public TokenSet? GetToken() { + var accessToken = _store.Get(AccessTokenKey); + var refreshToken = _store.Get(RefreshTokenKey); + var idToken = _store.Get(IdTokenKey); + + if (string.IsNullOrWhiteSpace(accessToken) && string.IsNullOrWhiteSpace(refreshToken) && string.IsNullOrWhiteSpace(idToken)) + return null; + return new TokenSet(accessToken ?? string.Empty, refreshToken ?? string.Empty, idToken ?? string.Empty); + } + + // 获取指定 token + public string? GetToken(string name) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Token name is required."); + return _store.Get(name); + } + + // 获取登录状态 + public LoginMeta? GetLoginMeta() { + var loginMetaJson = _store.Get(LoginMetaKey); + if (string.IsNullOrWhiteSpace(loginMetaJson)) + return null; + try { + return JsonSerializer.Deserialize(loginMetaJson); + } catch (JsonException) { + return null; // 可选:记录日志 + } + } + + // 清除全部 token + public void ClearToken() { + _store.Clear(AccessTokenKey); + _store.Clear(RefreshTokenKey); + _store.Clear(IdTokenKey); + _store.Clear(LoginMetaKey); + } + + // 清除指定 token + public void ClearToken(string name) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Token name is required."); + _store.Clear(name); + } + } + + +} diff --git a/AudioVisualizer/SSO/TokenSet.cs b/AudioVisualizer/SSO/TokenSet.cs new file mode 100644 index 0000000..76855c0 --- /dev/null +++ b/AudioVisualizer/SSO/TokenSet.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper.SSO { + public class TokenSet { + public readonly String? AccessToken; + public readonly String? RefreshToken; + public readonly String? IdToken; + /// + /// 构造函数 + /// + /// 访问令牌 + /// 刷新令牌 + /// Id令牌 + public TokenSet(String AccessToken,String RefreshToken,String IdToken) { + this.AccessToken = AccessToken; + this.RefreshToken = RefreshToken; + this.IdToken = IdToken; + } + } +} diff --git a/AudioVisualizer/SSO/UserInfoSet.cs b/AudioVisualizer/SSO/UserInfoSet.cs new file mode 100644 index 0000000..75c1ec1 --- /dev/null +++ b/AudioVisualizer/SSO/UserInfoSet.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace AudioWallpaper.SSO { + public class UserInfoSet { + public string sub { get; set; } + public bool email_verified { get; set; } + public string nickname { get; set; } + public string name { get; set; } + public string preferred_username { get; set; } + public string locale { get; set; } + public string given_name { get; set; } + public string family_name { get; set; } + public string email { get; set; } + + public UserInfoSet() { } + + public UserInfoSet(string json) { + var obj = JsonSerializer.Deserialize(json); + if (obj != null) { + sub = obj.sub; + email_verified = obj.email_verified; + nickname = obj.nickname?? obj.name; + name = obj.name; + preferred_username = obj.preferred_username; + locale = obj.locale; + given_name = obj.given_name; + family_name = obj.family_name; + email = obj.email; + } + } + } +} diff --git a/AudioVisualizer/SSO/WebView2Browser.cs b/AudioVisualizer/SSO/WebView2Browser.cs new file mode 100644 index 0000000..804fead --- /dev/null +++ b/AudioVisualizer/SSO/WebView2Browser.cs @@ -0,0 +1,101 @@ +using IdentityModel.OidcClient.Browser; +using Microsoft.Web.WebView2.WinForms; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AudioWallpaper.SSO { + + public class WebView2Browser : IBrowser { + private readonly int _windowWidth; + private readonly int _windowHeight; + private readonly Form? _form; + public WebView2Browser(int width = 600, int height = 800) { + _windowWidth = width; + _windowHeight = height; + } + public WebView2Browser(Form form, int width = 600, int height = 800) { + _windowWidth = width; + _windowHeight = height; + _form = form; + } + public WebView2Browser(Form form) { + _windowWidth = 600; + _windowHeight = 800; + _form = form; + } + + + public Task InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default) { + var tcs = new TaskCompletionSource(); + + var thread = new Thread(() => { + var form = new Form { + Width = _windowWidth, + Height = _windowHeight, + StartPosition = FormStartPosition.CenterScreen, + Text = "登录认证" + }; + + var webView = new WebView2 { + Dock = DockStyle.Fill + }; + + form.Controls.Add(webView); + + form.FormClosed += (s, e) => { + if (!tcs.Task.IsCompleted) { + tcs.TrySetResult(new BrowserResult { + ResultType = BrowserResultType.UserCancel, + Error = "User closed the login window" + }); + } + }; + + webView.NavigationStarting += (s, e) => { + if (e.Uri.StartsWith(options.EndUrl)) { + tcs.TrySetResult(new BrowserResult { + ResultType = BrowserResultType.Success, + Response = e.Uri + }); + form.Invoke(new Action(() => form.Close())); + } + }; + + webView.CoreWebView2InitializationCompleted += async (s, e) => { + if (e.IsSuccess) { + webView.CoreWebView2.Settings.AreDevToolsEnabled = false; + webView.CoreWebView2.ContextMenuRequested += (s, e) => { + e.Handled = true; + }; + webView.CoreWebView2.Navigate(options.StartUrl); + } else { + tcs.TrySetResult(new BrowserResult { + ResultType = BrowserResultType.UnknownError, + Error = "WebView2 initialization failed" + }); + form.Close(); + } + }; + + form.Load += async (s, e) => { + await webView.EnsureCoreWebView2Async(null); + }; + if (_form != null) { + _form.Invoke(new Action(() => { + form.ShowDialog(_form); + })); + } else { + Application.Run(form); + } + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + return tcs.Task; + } + } + +} diff --git a/AudioVisualizer/SSOManager.cs b/AudioVisualizer/SSOManager.cs new file mode 100644 index 0000000..212c012 --- /dev/null +++ b/AudioVisualizer/SSOManager.cs @@ -0,0 +1,259 @@ +using AudioWallpaper.Entity; +using AudioWallpaper.SSO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AudioWallpaper { + public class SSOManager { + public readonly static String StorageDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\AudioWallpaper"; + + private static TokenManager _tokenManager = new TokenManager(new EncryptedFileTokenStore(StorageDirectory)); + + public static UserInfoSet? userInfoSet = null; + + public SSOManager() { + AuthManager.Instance.OnLoginSuccess += OnLoginSuccess; + AuthManager.Instance.OnLoginFailed += OnloginFailed; + AuthManager.Instance.OnLogoutSuccess += OnLogoutSuccess; + AuthManager.Instance.OnLogoutFailed += OnLogoutFailed; + AuthManager.Instance.OnRevokeTokenSuccess += OnRevokeTokenSuccess; + AuthManager.Instance.OnRevokeTokenFailed += OnRevokeTokenFailed; + AuthManager.Instance.OnRefreshTokenSuccess += OnRefreshTokenSuccess; + AuthManager.Instance.OnRefreshTokenFailed += OnRefreshTokenFailed; + AuthManager.Instance.OnGetUserInfoSuccess += OnGetUserInfoSuccess; + AuthManager.Instance.OnGetUserInfoFailed += OnGetUserInfoFailed; + AuthManager.Instance.OnAuthStatusChanged += OnAuthStatusChanged; + Console.WriteLine(StorageDirectory); + } + + + + public async Task Initialize() { + + await ReLogin(); + + Console.WriteLine("sso初始化完成"); + + } + #region 登录事件回调处理 + /// + /// 退出登录失败回调 + /// + /// 错误信息 + private void OnLogoutFailed(string obj) { + Console.WriteLine("登出失败:" + obj); + } + /// + /// 退出登录成功回调 + /// + private void OnLogoutSuccess() { + Console.WriteLine("登出成功"); + //清除本地存储的令牌 + _tokenManager.ClearToken(); + if (SetFrom.setFrom == null) { + return; + } + SetFrom.setFrom.ApplyModifications( + new UIModification { ControlName = "P_NO_Login", PropertyName = "Visible", Value = true }, + new UIModification { ControlName = "P_Login", PropertyName = "Visible", Value = false }, + new UIModification { ControlName = "L_UserName", PropertyName = "Text", Value = string.Empty }, + new UIModification { ControlName = "L_Welcome", PropertyName = "Text", Value = string.Empty } + ); + + } + /// + /// 撤销令牌失败回调 + /// + /// 错误信息 + private void OnRevokeTokenFailed(string obj) { + Console.WriteLine("撤销令牌失败:" + obj); + } + /// + /// 撤销令牌成功回调 + /// + private void OnRevokeTokenSuccess() { + Console.WriteLine("撤销令牌成功"); + //清除本地存储的令牌 + _tokenManager.ClearToken(); + } + + /// + /// 刷新令牌失败回调 + /// + /// 失败信息 + private void OnRefreshTokenFailed(string obj) { + Console.WriteLine("刷新令牌失败:" + obj); + //重新登录 + //ReLogin(); + } + /// + /// 刷新令牌成功回调 + /// + /// token内容 + private void OnRefreshTokenSuccess(TokenSet set) { + + Console.WriteLine("令牌刷新成功:"); + Console.WriteLine("AccessToken: " + set.AccessToken); + Console.WriteLine("RefreshToken: " + set.RefreshToken); + Console.WriteLine("IdToken: " + set.IdToken); + //保存Token + _tokenManager.SaveToken(set); + AuthManager.Instance.GetUserInfo(set.AccessToken); + Console.WriteLine($"登陆状态{AuthManager.Instance.AuthStatus}"); + } + + /// + /// 获取用户信息失败回调 + /// + /// 失败信息 + private void OnGetUserInfoFailed(string obj) { + Console.WriteLine("获取用户信息失败:" + obj); + } + /// + /// 获取用户信息成功回调 + /// + /// 用户信息 + private void OnGetUserInfoSuccess(UserInfoSet set) { + Console.WriteLine("获取用户信息成功:"); + userInfoSet = set; + _tokenManager.SaveLoginMeta(new LoginMeta { + IsLoggedIn = true, + UserInfo = set + }); + + // 更新登录状态 + + + if (SetFrom.setFrom == null) { + return; + } + SetFrom.setFrom.ApplyModifications( + new UIModification { ControlName = "P_NO_Login", PropertyName = "Visible", Value = false }, + new UIModification { ControlName = "P_Login", PropertyName = "Visible", Value = true }, + new UIModification { ControlName = "L_UserName", PropertyName = "Text", Value = set.name }, + new UIModification { ControlName = "L_Welcome", PropertyName = "Text", Value = $"欢迎回来,{userInfoSet.nickname}!" } + ); + + + } + /// + /// 登录成功回调 + /// + /// + private void OnLoginSuccess(TokenSet set) { + Console.WriteLine("登录成功"); + Console.WriteLine("AccessToken: " + set.AccessToken); + Console.WriteLine("RefreshToken: " + set.RefreshToken); + Console.WriteLine("IdToken: " + set.IdToken); + if (set != null + && !string.IsNullOrWhiteSpace(set.AccessToken) + && !string.IsNullOrWhiteSpace(set.RefreshToken) + && !string.IsNullOrWhiteSpace(set.IdToken)) { + //保存Token + _tokenManager.SaveToken(set); + //获取用户信息 + AuthManager.Instance.GetUserInfo(set.AccessToken); + + } else { + Console.WriteLine("TokenSet为空"); + } + } + private void OnloginFailed(String error) { + Console.WriteLine("登录失败"); + Console.WriteLine(error); + } + private async Task ReLogin() { + TokenSet? tokenSet = _tokenManager.GetToken(); + if (tokenSet != null && !string.IsNullOrWhiteSpace(tokenSet.RefreshToken)) { + Console.WriteLine(tokenSet.RefreshToken); + await AuthManager.Instance.RefreshToken(tokenSet.RefreshToken); + } + + } + /// + /// 登录状态发生改变回调 + /// + /// 当前状态 + /// + private void OnAuthStatusChanged(AuthStatus status) { + Console.WriteLine($"登录状态回调,当前状态{status}"); + if (status == AuthStatus.Success) { + AccAssManage.Instance?.UpdateLoginState(new LoginMeta() { + IsLoggedIn = true, + UserInfo = null + }); + } + if(status == AuthStatus.LoginOut || status == AuthStatus.Failed) { + AccAssManage.Instance?.UpdateLoginState(new LoginMeta() { + IsLoggedIn = false, + UserInfo = null + }); + } + + } + #endregion + + #region 对外提供的方法 + /// + /// 获取用户访问令牌 + /// + /// 访问令牌,如果令牌不存在返回空字符串 + public String? GetAccessToken() { + var tokenSet = _tokenManager.GetToken(); + if (tokenSet != null) { + return tokenSet.AccessToken; + } + return string.Empty; + } + + /// + /// 获取用户刷新令牌 + /// + /// 刷新令牌,如果令牌不存在返回空字符串 + public String? GetRefreshToken() { + var tokenSet = _tokenManager.GetToken(); + if (tokenSet != null) { + return tokenSet.RefreshToken; + } + return string.Empty; + } + /// + /// 获取用户ID令牌 + /// + /// ID令牌 + public String? GetIdToken() { + var tokenSet = _tokenManager.GetToken(); + if (tokenSet != null) { + return tokenSet.IdToken; + } + return string.Empty; + } + + public void StartLogin(Form form) { + AuthManager.Instance.Login(form); + } + public void Logout() { + AuthManager.Instance.Logout(_tokenManager.GetToken()); + } + + public void RefreshToken() { + AuthManager.Instance.RefreshToken(_tokenManager.GetToken()?.RefreshToken); + } + public void GetUserInfo() { + AuthManager.Instance.GetUserInfo(_tokenManager.GetToken()?.AccessToken); + } + public AuthStatus GetAuthStatus() { + return AuthManager.Instance.AuthStatus; + } + public LoginMeta? GetLoginMeta() { + return _tokenManager.GetLoginMeta(); + } + + #endregion + + + } +} diff --git a/AudioVisualizer/SetForm.Designer.cs b/AudioVisualizer/SetForm.Designer.cs index 32eaa35..f81b178 100644 --- a/AudioVisualizer/SetForm.Designer.cs +++ b/AudioVisualizer/SetForm.Designer.cs @@ -55,6 +55,8 @@ buttonReset = new Button(); buttonSave = new Button(); Display = new GroupBox(); + numericUpDownStripeSpacing = new NumericUpDown(); + label4 = new Label(); label2 = new Label(); checkBoxSmoothStripe = new CheckBox(); checkBoxBottomEdge = new CheckBox(); @@ -75,6 +77,22 @@ labelSpectralShift = new Label(); labelRhythmicMagnification = new Label(); Other = new TabPage(); + accompanyingAssistant = new GroupBox(); + P_Login = new Panel(); + NUD_Interval = new NumericUpDown(); + L_Interval = new Label(); + CB_TIme_Range = new ComboBox(); + L_Time_Ranges = new Label(); + TB_Bucket_Input = new TextBox(); + L_Bucket_Name = new Label(); + B_Logout = new Button(); + CB_Use_Aca = new CheckBox(); + L_Use_Aca = new Label(); + L_UserName = new Label(); + L_Welcome = new Label(); + P_NO_Login = new Panel(); + P_NO_Login_Text = new Label(); + B_Login = new Button(); ThirdParty = new GroupBox(); CheckBoxAutoStop = new CheckBox(); OtherDisplayName = new Label(); @@ -90,8 +108,6 @@ label1 = new Label(); OtherResetBtn = new Button(); OtherSaveBtn = new Button(); - label4 = new Label(); - numericUpDownStripeSpacing = new NumericUpDown(); tabControl.SuspendLayout(); Routine.SuspendLayout(); MultiMonitorCompatibility.SuspendLayout(); @@ -102,16 +118,20 @@ ((System.ComponentModel.ISupportInitialize)StartColor).BeginInit(); ((System.ComponentModel.ISupportInitialize)StopColor).BeginInit(); Display.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)numericUpDownStripeSpacing).BeginInit(); Effect.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)numericUpDownFourierVariation).BeginInit(); ((System.ComponentModel.ISupportInitialize)numericUpDownSpectralShift).BeginInit(); ((System.ComponentModel.ISupportInitialize)numericUpDownRhythmicMagnification).BeginInit(); Other.SuspendLayout(); + accompanyingAssistant.SuspendLayout(); + P_Login.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)NUD_Interval).BeginInit(); + P_NO_Login.SuspendLayout(); ThirdParty.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)trackBarVideoVolume).BeginInit(); ((System.ComponentModel.ISupportInitialize)trackBarVideoRate).BeginInit(); ((System.ComponentModel.ISupportInitialize)videoView1).BeginInit(); - ((System.ComponentModel.ISupportInitialize)numericUpDownStripeSpacing).BeginInit(); SuspendLayout(); // // tabControl @@ -349,7 +369,7 @@ // // numericUpDownNumberOfColors // - numericUpDownNumberOfColors.Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point); + numericUpDownNumberOfColors.Font = new Font("Microsoft YaHei UI", 9F); numericUpDownNumberOfColors.Location = new Point(99, 89); numericUpDownNumberOfColors.Maximum = new decimal(new int[] { 51200, 0, 0, 0 }); numericUpDownNumberOfColors.Minimum = new decimal(new int[] { 4, 0, 0, 0 }); @@ -458,6 +478,25 @@ Display.TabStop = false; Display.Text = "显示内容"; // + // numericUpDownStripeSpacing + // + numericUpDownStripeSpacing.Location = new Point(225, 68); + numericUpDownStripeSpacing.Maximum = new decimal(new int[] { 1000, 0, 0, 0 }); + numericUpDownStripeSpacing.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); + numericUpDownStripeSpacing.Name = "numericUpDownStripeSpacing"; + numericUpDownStripeSpacing.Size = new Size(45, 23); + numericUpDownStripeSpacing.TabIndex = 9; + numericUpDownStripeSpacing.Value = new decimal(new int[] { 1, 0, 0, 0 }); + // + // label4 + // + label4.AutoSize = true; + label4.Location = new Point(163, 71); + label4.Name = "label4"; + label4.Size = new Size(56, 17); + label4.TabIndex = 16; + label4.Text = "条纹间隔"; + // // label2 // label2.AutoSize = true; @@ -646,6 +685,7 @@ // // Other // + Other.Controls.Add(accompanyingAssistant); Other.Controls.Add(ThirdParty); Other.Controls.Add(OtherResetBtn); Other.Controls.Add(OtherSaveBtn); @@ -657,6 +697,164 @@ Other.Text = "其他"; Other.UseVisualStyleBackColor = true; // + // accompanyingAssistant + // + accompanyingAssistant.Controls.Add(P_Login); + accompanyingAssistant.Controls.Add(P_NO_Login); + accompanyingAssistant.Location = new Point(5, 300); + accompanyingAssistant.Name = "accompanyingAssistant"; + accompanyingAssistant.Size = new Size(290, 360); + accompanyingAssistant.TabIndex = 7; + accompanyingAssistant.TabStop = false; + accompanyingAssistant.Text = "陪伴助手"; + // + // P_Login + // + P_Login.Controls.Add(NUD_Interval); + P_Login.Controls.Add(L_Interval); + P_Login.Controls.Add(CB_TIme_Range); + P_Login.Controls.Add(L_Time_Ranges); + P_Login.Controls.Add(TB_Bucket_Input); + P_Login.Controls.Add(L_Bucket_Name); + P_Login.Controls.Add(B_Logout); + P_Login.Controls.Add(CB_Use_Aca); + P_Login.Controls.Add(L_Use_Aca); + P_Login.Controls.Add(L_UserName); + P_Login.Controls.Add(L_Welcome); + P_Login.Location = new Point(5, 20); + P_Login.Name = "P_Login"; + P_Login.Size = new Size(280, 330); + P_Login.TabIndex = 25; + P_Login.Visible = false; + // + // NUD_Interval + // + NUD_Interval.Location = new Point(102, 128); + NUD_Interval.Maximum = new decimal(new int[] { 21600, 0, 0, 0 }); + NUD_Interval.Minimum = new decimal(new int[] { 5, 0, 0, 0 }); + NUD_Interval.Name = "NUD_Interval"; + NUD_Interval.Size = new Size(100, 23); + NUD_Interval.TabIndex = 32; + NUD_Interval.Value = new decimal(new int[] { 5, 0, 0, 0 }); + // + // L_Interval + // + L_Interval.AutoSize = true; + L_Interval.Location = new Point(14, 130); + L_Interval.Name = "L_Interval"; + L_Interval.Size = new Size(56, 17); + L_Interval.TabIndex = 31; + L_Interval.Text = "请求间隔"; + // + // CB_TIme_Range + // + CB_TIme_Range.FormattingEnabled = true; + CB_TIme_Range.Location = new Point(102, 97); + CB_TIme_Range.Name = "CB_TIme_Range"; + CB_TIme_Range.Size = new Size(100, 25); + CB_TIme_Range.TabIndex = 30; + // + // L_Time_Ranges + // + L_Time_Ranges.AutoSize = true; + L_Time_Ranges.Location = new Point(14, 100); + L_Time_Ranges.Name = "L_Time_Ranges"; + L_Time_Ranges.Size = new Size(56, 17); + L_Time_Ranges.TabIndex = 29; + L_Time_Ranges.Text = "数据区域"; + // + // TB_Bucket_Input + // + TB_Bucket_Input.Location = new Point(102, 68); + TB_Bucket_Input.Name = "TB_Bucket_Input"; + TB_Bucket_Input.Size = new Size(100, 23); + TB_Bucket_Input.TabIndex = 28; + // + // L_Bucket_Name + // + L_Bucket_Name.AutoSize = true; + L_Bucket_Name.Location = new Point(14, 71); + L_Bucket_Name.Name = "L_Bucket_Name"; + L_Bucket_Name.Size = new Size(47, 17); + L_Bucket_Name.TabIndex = 27; + L_Bucket_Name.Text = "Bucket"; + // + // B_Logout + // + B_Logout.Location = new Point(102, 181); + B_Logout.Name = "B_Logout"; + B_Logout.Size = new Size(75, 23); + B_Logout.TabIndex = 26; + B_Logout.Text = "退出登录"; + B_Logout.UseVisualStyleBackColor = true; + B_Logout.Click += B_Logout_Click; + // + // CB_Use_Aca + // + CB_Use_Aca.AutoSize = true; + CB_Use_Aca.Location = new Point(187, 41); + CB_Use_Aca.Name = "CB_Use_Aca"; + CB_Use_Aca.Size = new Size(15, 14); + CB_Use_Aca.TabIndex = 25; + CB_Use_Aca.UseVisualStyleBackColor = true; + CB_Use_Aca.CheckedChanged += CB_Use_Aca_CheckedChanged; + // + // L_Use_Aca + // + L_Use_Aca.AutoSize = true; + L_Use_Aca.Location = new Point(14, 45); + L_Use_Aca.Name = "L_Use_Aca"; + L_Use_Aca.Size = new Size(80, 17); + L_Use_Aca.TabIndex = 2; + L_Use_Aca.Text = "启用陪伴助手"; + // + // L_UserName + // + L_UserName.AutoSize = true; + L_UserName.Location = new Point(14, 21); + L_UserName.Name = "L_UserName"; + L_UserName.Size = new Size(56, 17); + L_UserName.TabIndex = 1; + L_UserName.Text = "用户名:"; + // + // L_Welcome + // + L_Welcome.AutoSize = true; + L_Welcome.Location = new Point(10, 4); + L_Welcome.Name = "L_Welcome"; + L_Welcome.Size = new Size(35, 17); + L_Welcome.TabIndex = 0; + L_Welcome.Text = "欢迎:"; + // + // P_NO_Login + // + P_NO_Login.Controls.Add(P_NO_Login_Text); + P_NO_Login.Controls.Add(B_Login); + P_NO_Login.Location = new Point(164, 1); + P_NO_Login.Name = "P_NO_Login"; + P_NO_Login.Size = new Size(280, 330); + P_NO_Login.TabIndex = 1; + // + // P_NO_Login_Text + // + P_NO_Login_Text.AutoSize = true; + P_NO_Login_Text.Font = new Font("Microsoft YaHei UI", 16F); + P_NO_Login_Text.Location = new Point(7, 24); + P_NO_Login_Text.Name = "P_NO_Login_Text"; + P_NO_Login_Text.Size = new Size(255, 30); + P_NO_Login_Text.TabIndex = 1; + P_NO_Login_Text.Text = "陪伴助手需要登录后使用"; + // + // B_Login + // + B_Login.Location = new Point(110, 137); + B_Login.Name = "B_Login"; + B_Login.Size = new Size(75, 23); + B_Login.TabIndex = 0; + B_Login.Text = "登录"; + B_Login.UseVisualStyleBackColor = true; + B_Login.Click += button1_Click; + // // ThirdParty // ThirdParty.Controls.Add(CheckBoxAutoStop); @@ -818,25 +1016,6 @@ OtherSaveBtn.UseVisualStyleBackColor = true; OtherSaveBtn.Click += OtherSaveBtn_Click; // - // label4 - // - label4.AutoSize = true; - label4.Location = new Point(163, 71); - label4.Name = "label4"; - label4.Size = new Size(56, 17); - label4.TabIndex = 16; - label4.Text = "条纹间隔"; - // - // numericUpDownStripeSpacing - // - numericUpDownStripeSpacing.Location = new Point(225, 68); - numericUpDownStripeSpacing.Maximum = new decimal(new int[] { 1000, 0, 0, 0 }); - numericUpDownStripeSpacing.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); - numericUpDownStripeSpacing.Name = "numericUpDownStripeSpacing"; - numericUpDownStripeSpacing.Size = new Size(45, 23); - numericUpDownStripeSpacing.TabIndex = 9; - numericUpDownStripeSpacing.Value = new decimal(new int[] { 1, 0, 0, 0 }); - // // SetFrom // AutoScaleDimensions = new SizeF(7F, 17F); @@ -864,18 +1043,24 @@ ((System.ComponentModel.ISupportInitialize)StopColor).EndInit(); Display.ResumeLayout(false); Display.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)numericUpDownStripeSpacing).EndInit(); Effect.ResumeLayout(false); Effect.PerformLayout(); ((System.ComponentModel.ISupportInitialize)numericUpDownFourierVariation).EndInit(); ((System.ComponentModel.ISupportInitialize)numericUpDownSpectralShift).EndInit(); ((System.ComponentModel.ISupportInitialize)numericUpDownRhythmicMagnification).EndInit(); Other.ResumeLayout(false); + accompanyingAssistant.ResumeLayout(false); + P_Login.ResumeLayout(false); + P_Login.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)NUD_Interval).EndInit(); + P_NO_Login.ResumeLayout(false); + P_NO_Login.PerformLayout(); ThirdParty.ResumeLayout(false); ThirdParty.PerformLayout(); ((System.ComponentModel.ISupportInitialize)trackBarVideoVolume).EndInit(); ((System.ComponentModel.ISupportInitialize)trackBarVideoRate).EndInit(); ((System.ComponentModel.ISupportInitialize)videoView1).EndInit(); - ((System.ComponentModel.ISupportInitialize)numericUpDownStripeSpacing).EndInit(); ResumeLayout(false); } @@ -949,5 +1134,21 @@ private CheckBox checkBoxSmoothStripe; private NumericUpDown numericUpDownStripeSpacing; private Label label4; + private GroupBox accompanyingAssistant; + private Button B_Login; + private Panel P_NO_Login; + private Label P_NO_Login_Text; + private Panel P_Login; + private Label L_Use_Aca; + private Label L_UserName; + private Label L_Welcome; + private Button B_Logout; + private CheckBox CB_Use_Aca; + private Label L_Bucket_Name; + private TextBox TB_Bucket_Input; + private Label L_Time_Ranges; + private Label L_Interval; + private ComboBox CB_TIme_Range; + private NumericUpDown NUD_Interval; } } \ No newline at end of file diff --git a/AudioVisualizer/SetForm.cs b/AudioVisualizer/SetForm.cs index f43d6cd..eec1aba 100644 --- a/AudioVisualizer/SetForm.cs +++ b/AudioVisualizer/SetForm.cs @@ -1,4 +1,6 @@ -using AudioWallpaper.Entity; +using AudioWallpaper.ActivityWatch; +using AudioWallpaper.Entity; +using AudioWallpaper.SSO; using LibVLCSharp.Shared; using System.Runtime.CompilerServices; @@ -7,7 +9,7 @@ namespace AudioWallpaper { public static String ConfigFilePath = Application.StartupPath + "\\config.ini"; public String? BackImagePath = null; public String? OtherVideoPath = null; - private static SetFrom? setFrom = null; + public static SetFrom? setFrom = null; private Screen NowWindowOnScrenn; private System.Timers.Timer debounceTimer; private const double DEBOUNCE_TIME = 150; @@ -16,18 +18,16 @@ namespace AudioWallpaper { private MediaPlayer? mediaPlayer; private Media? media; private String[] windowFunc = new String[] { "RECTANGULAR", "HANNING", "HAMMING", "BLACKMAN", "BARTLETT" }; + private Dictionary> timeRanges = ActivityWatchClient.GenerateTimeRanges(); + private static TokenManager? _tokenManager; private SetFrom() { debounceTimer = new System.Timers.Timer(DEBOUNCE_TIME); debounceTimer.Elapsed += DebounceTimer_Tick; debounceTimer.AutoReset = false; - } public delegate void ReLoadConfig(ConfigurationObject configurationObject); public event ReLoadConfig ReloadConfig; - - - public static SetFrom ShowSetFrom() { if (setFrom == null) { setFrom = new SetFrom(); @@ -36,6 +36,11 @@ namespace AudioWallpaper { foreach (string item in setFrom.windowFunc) { setFrom.selectWindowFunc.Items.Add(item); } + setFrom.CB_TIme_Range.Items.Clear(); + + foreach (var key in setFrom.timeRanges.Keys) { + setFrom.CB_TIme_Range.Items.Add(key); + } } return setFrom; } @@ -82,15 +87,25 @@ namespace AudioWallpaper { }; videoWallpaperConfigObject.SaveConfig(ConfigFilePath); - + OtherConfigObjects otherConfigObjects = new OtherConfigObjects { + DisplayName = DisplayName.Text, + UseAccompanyingAssistant = CB_Use_Aca.Checked, + BucketName = TB_Bucket_Input.Text, + RangeTime = CB_TIme_Range.SelectedItem != null ? CB_TIme_Range.SelectedItem.ToString() : "", + Interval = (int)NUD_Interval.Value + }; + otherConfigObjects.SaveConfig(ConfigFilePath); + //创建配置对象 ConfigurationObject configurationObject = new ConfigurationObject() { GeneralConfigurationObjects = generalConfigurationObjects, - VideoWallpaperConfigObject = videoWallpaperConfigObject + VideoWallpaperConfigObject = videoWallpaperConfigObject, + OtherConfigObjects = otherConfigObjects }; if (ReloadConfig != null) { ReloadConfig(configurationObject); } + GC.Collect(); } /// /// 设置窗体加载事件 @@ -102,6 +117,27 @@ namespace AudioWallpaper { //appBarManage ShowSetData(); ControlStatusUpdates(); + + _tokenManager = new TokenManager(new EncryptedFileTokenStore(SSOManager.StorageDirectory)); + + LoginMeta? loginMeta = _tokenManager.GetLoginMeta(); + if (loginMeta != null && loginMeta.IsLoggedIn) { + P_Login.Visible = true; + P_NO_Login.Visible = false; + UserInfoSet? userInfoSet = loginMeta.UserInfo; + if (userInfoSet == null) { + return; + } + L_UserName.Text = userInfoSet.name; + L_Welcome.Text = $"欢迎回来,{userInfoSet.nickname}!"; + + + } else { + P_Login.Visible = false; + P_NO_Login.Visible = true; + + } + } public void ShowSetData() { @@ -113,7 +149,7 @@ namespace AudioWallpaper { ConfigurationObject configurationObject = new ConfigurationObject(); GeneralConfigurationObjects generalConfigurationObjects = configurationObject.GeneralConfigurationObjects.LoadConfiguration(ConfigFilePath, NowWindowOnScrenn.DeviceName); VideoWallpaperConfigObject videoWallpaperConfig = configurationObject.VideoWallpaperConfigObject.LoadConfig(ConfigFilePath, NowWindowOnScrenn.DeviceName); - + OtherConfigObjects otherConfigObjects = configurationObject.OtherConfigObjects.LoadConfig(ConfigFilePath, NowWindowOnScrenn.DeviceName); numericUpDownRhythmicMagnification.Value = (decimal)generalConfigurationObjects.DefaultRadical; numericUpDownSpectralShift.Value = (decimal)generalConfigurationObjects.DefaultOffset; numericUpDownFourierVariation.Value = generalConfigurationObjects.DefaultFourierVariation; @@ -150,7 +186,13 @@ namespace AudioWallpaper { CheckBoxAutoStop.Checked = videoWallpaperConfig.AutoStopWallPaper; videoViewPlayer(OtherVideoPath); + CB_Use_Aca.Checked = otherConfigObjects.UseAccompanyingAssistant; + TB_Bucket_Input.Text = otherConfigObjects.BucketName ?? string.Empty; + CB_TIme_Range.SelectedIndex = CB_TIme_Range.Items.Cast().Select((item, index) => new { item, index }).FirstOrDefault(x => string.Equals(x.item.ToString(), otherConfigObjects.RangeTime, StringComparison.OrdinalIgnoreCase))?.index ?? 0; + NUD_Interval.Value = otherConfigObjects.Interval > 0 ? otherConfigObjects.Interval : 60 * 5; + //gc + GC.Collect(); } private void ButtonReset_Click(object sender, EventArgs e) { GeneralConfigurationObjects generalConfigurationObjects = new GeneralConfigurationObjects(); @@ -291,7 +333,7 @@ namespace AudioWallpaper { /// /// private void videoView1_Click(object sender, EventArgs e) { - + OpenFileDialog dialog = new OpenFileDialog(); dialog.Filter = "视频(*.MP4;*.AVI;*.MKV;*.MOV;*.WMV)|*.MP4;*.AVI;*.MKV;*.MOV;*.WMV"; dialog.Title = "请选择一个视频文件作为壁纸"; @@ -335,7 +377,8 @@ namespace AudioWallpaper { uri = new Uri(url); } catch (Exception) { return; - }; + } + ; if (uri == null) { return; } @@ -363,6 +406,9 @@ namespace AudioWallpaper { if (media != null && mediaPlayer != null) { ThreadPool.QueueUserWorkItem((p) => mediaPlayer.Play(media)); } + } + private void CB_Use_Aca_CheckedChanged(object sender, EventArgs e) { + } private void DisposeVideo() { if (mediaPlayer != null) { @@ -393,5 +439,37 @@ namespace AudioWallpaper { videoWallpaperConfigObject.SaveConfig(ConfigFilePath); ShowSetData(); } + + private void button1_Click(object sender, EventArgs e) { + Program.SSOM.StartLogin(this); + } + private void B_Logout_Click(object sender, EventArgs e) { + Program.SSOM.Logout(); + } + #region 对外提供一个方法来应用UI修改 + public void ApplyModifications(params UIModification[] modifications) { + if (InvokeRequired) { + Invoke(new MethodInvoker(() => ApplyModifications(modifications))); + return; + } + + foreach (var mod in modifications) { + if (mod.ControlName == null) { + continue; + } + if (mod.PropertyName == null) { + continue; + } + var ctrl = Controls.Find(mod.ControlName, true).FirstOrDefault(); + if (ctrl == null) continue; + + var prop = ctrl.GetType().GetProperty(mod.PropertyName); + if (prop != null && prop.CanWrite) { + prop.SetValue(ctrl, Convert.ChangeType(mod.Value, prop.PropertyType)); + } + } + } + #endregion + } } diff --git a/AudioVisualizer/SetForm.resx b/AudioVisualizer/SetForm.resx index b67c184..601e224 100644 --- a/AudioVisualizer/SetForm.resx +++ b/AudioVisualizer/SetForm.resx @@ -1,7 +1,7 @@  - - - <?vlc gettext("VLC media player - Web Interface") ?> - - - - - - - - - - - - - - - -
-
-
-
-
    -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
-
    -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
-
-
volume
-
50%
-
-
- Album Art -
-
-
-
">
-
00:00:00
-
00:00:00
-
-
-
-
-
    -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
  • ">
  • -
-
-
-
-
-
-


-
-
-
- -
-
-
"> -

- -

-

- -

-
-
"> -

- Main Controls will operate the stream instead of the main interface.") ?> - Manage Streams") ?> - "> -

-

- Media Viewer window will display the stream.") ?> - Main Controls.") ?> -

-

- Library will be the subject of the stream.") ?> -

-

- Open Stream button again.") ?> -

-

- -

-
- - - diff --git a/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/common.js b/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/common.js deleted file mode 100644 index d766699..0000000 --- a/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/common.js +++ /dev/null @@ -1,93 +0,0 @@ -var intv = 0; -var ccmd = ""; -var video_types = [ - "asf", "avi", "bik", "bin", "divx", "drc", "dv", "f4v", "flv", "gxf", "iso", - "m1v", "m2v", "m2t", "m2ts", "m4v", "mkv", "mov", - "mp2", "mp4", "mpeg", "mpeg1", - "mpeg2", "mpeg4", "mpg", "mts", "mtv", "mxf", "mxg", "nuv", - "ogg", "ogm", "ogv", "ogx", "ps", - "rec", "rm", "rmvb", "rpl", "thp", "ts", "txd", "vob", "wmv", "xesc" ]; -var audio_types = [ - "3ga", "a52", "aac", "ac3", "ape", "awb", "dts", "flac", "it", - "m4a", "m4p", "mka", "mlp", "mod", "mp1", "mp2", "mp3", - "oga", "ogg", "oma", "s3m", "spx", "thd", "tta", - "wav", "wma", "wv", "xm" -]; -var playlist_types = [ - "asx", "b4s", "cue", "ifo", "m3u", "m3u8", "pls", "ram", "rar", - "sdp", "vlc", "xspf", "zip", "conf" -]; - -var stream_server = window.location.hostname; - -function format_time(s) { - var hours = Math.floor(s / 3600); - var minutes = Math.floor((s / 60) % 60); - var seconds = Math.floor(s % 60); - hours = hours < 10 ? "0" + hours : hours; - minutes = minutes < 10 ? "0" + minutes : minutes; - seconds = seconds < 10 ? "0" + seconds : seconds; - return hours + ":" + minutes + ":" + seconds; -} - -function toFloat(text) { - return parseFloat(text.replace(',', '.')); -} - -function setIntv() { - if (intv > 0) { - intv++; - setTimeout(setIntv, 500); - } else { - intv = 0; - } - if (intv > 5) { - var nt = 0; - switch (ccmd) { - case 'prev': - nt = Math.max(0, $('#seekSlider').slider('value') - 10); - break; - case 'next': - nt = Math.max(0, $('#seekSlider').slider('value') + 10); - break; - } - switch (current_que) { - case 'main': - sendCommand({ - 'command': 'seek', - 'val': Math.round((nt / 100) * $('#seekSlider').attr('totalLength')), - plreload: false - }); - break; - case 'stream': - sendVLMCmd('control Current seek ' + nt); - break; - } - } -} - -function isMobile() { - var a = navigator.userAgent || navigator.vendor || window.opera; - if (/android|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) { - return true; - } - return false; -} - -function createElementLi(name, type, dir, ext) { - var icon = "Other-48.png"; - if( type == "dir" && name == '..' ) - icon = "Back-48.png"; - else if( type == 'dir' ) - icon = "Folder-48.png"; - else if( $.inArray(ext, video_types) != -1 ) - icon = "Video-48.png"; - else if( $.inArray(ext, audio_types) != -1 ) - icon = "Audio-48.png"; - else if( $.inArray(ext, playlist_types) != -1 ) - // TODO: Playlist-48.png - icon = "Other-48.png"; - var open = type == "dir" ? "opendir='" + dir + "'" : (type == "file" ? "openfile='" + dir + "'" : "opendev='" + dir + "'"); - var str = "
  • " + name + "
    " + name + "
  • "; - return str; -} diff --git a/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/controllers.js b/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/controllers.js deleted file mode 100644 index c7bbdb6..0000000 --- a/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/controllers.js +++ /dev/null @@ -1,532 +0,0 @@ -var currentArt = null; -var current_que = 'main'; -var current_playlist_id = -1; -var previous_playlist_id = -1; -var seek_sec = 0; - -function updateArt(url) { - $('#albumArt').fadeOut(500, function () { - $(this).addClass('hidden').removeAttr('height').removeAttr('width').attr('src', url); - }); -} - -function updateStatus() { - $.ajax({ - url: 'requests/status.xml', - success: function (data, status, jqXHR) { - if (current_que == 'main') { - $('.dynamic').empty(); - $('#mediaTitle').append($('[name="filename"]', data).text()); - $('#totalTime').append(format_time($('length', data).text())); - $('#currentTime').append(format_time($('time', data).text())); - if (!$('#seekSlider').data('clicked')) { - $('#seekSlider').slider({ - value: toFloat($('position', data).text()) * 100 - }); - } - $('#currentVolume').append(Math.round($('volume', data).text() / 2.56) + '%'); - /* Don't interfere with the user's action */ - if (!$('#volumeSlider').data('clicked')) { - $('#volumeSlider').slider({ - value: ($('volume', data).text() / 5.12) - }); - } - $('#rateSlider').slider({ - value: ($('rate', data).text()) - }); - $('#currentRate').append(Math.round($('rate', data).text() * 100) / 100 + 'x'); - $('#audioSlider').slider({ - value: ($('audiodelay', data).text()) - }); - $('#currentAudioDelay').append(Math.round($('audiodelay', data).text() * 100) / 100 + 's'); - $('#subtitleSlider').slider({ - value: ($('subtitledelay', data).text()) - }); - $('#currentSubtitleDelay').append(Math.round($('subtitledelay', data).text() * 100) / 100 + 's'); - $('#seekSlider').attr('totalLength', $('length', data).text()); - $('#buttonPlay').attr('state', $('state', data).text()).attr('mrl', $('[name="filename"]', data).text()); - if ($('state', data).text() == 'playing') { - $('#buttonPlay').removeClass('paused').addClass('playing'); - } else { - $('#buttonPlay').removeClass('playing').addClass('paused'); - } - if ($('random', data).text() == 'true') { - $('#buttonShuffle').removeClass('ui-state-default').addClass('ui-state-active'); - } else { - $('#buttonShuffle').addClass('ui-state-default').removeClass('ui-state-active'); - } - if ($('loop', data).text() == 'true') { - $('#buttonLoop').removeClass('ui-state-default').addClass('ui-state-active'); - } else { - $('#buttonLoop').addClass('ui-state-default').removeClass('ui-state-active'); - } - if ($('repeat', data).text() == 'true') { - $('#buttonRepeat').removeClass('ui-state-default').addClass('ui-state-active'); - } else { - $('#buttonRepeat').addClass('ui-state-default').removeClass('ui-state-active'); - } - - if ($('[name="artwork_url"]', data).text() != currentArt && $('[name="artwork_url"]', data).text() != "") { - var tmp = new Date(); - currentArt = $('[name="artwork_url"]', data).text(); - updateArt('/art?' + tmp.getTime()); - } else if ($('[name="artwork_url"]', data).text() == "" && currentArt != 'images/vlc-48.png') { - currentArt = 'images/vlc-48.png'; - updateArt(currentArt); - } - - current_playlist_id = parseInt($('currentplid', data).text()); - if (previous_playlist_id != current_playlist_id) { - updatePlayList(); - previous_playlist_id = current_playlist_id; - } - - seek_sec = parseInt($('seek_sec', data).text()); - - if (pollStatus) { - setTimeout(updateStatus, 1000); - } - - } - $('band', data).each(function () { - var id = $(this).attr('id'); - var value = $(this).text() ? $(this).text() : 0; - var freq = ["60 Hz","170 Hz", "310 Hz", "600 Hz", "1 kHz","3 kHz", "6 kHz", "12 kHz" , "14 kHz" , "16 kHz" ]; - if (!$('#eq_container' + id).length) { - $('#window_equalizer').append('
    ' + value + 'dB
    ' + freq[id] + '
    '); - $('#eq' + id).slider({ - min: -20, - max: 20, - step: 0.1, - range: "min", - value: value, - animate: true, - orientation: "vertical", - stop: function (event, ui) { - $('#' + $(this).attr('id') + '_txt').empty().append(ui.value + 'dB'); - sendCommand({ - command: 'equalizer', - val: ui.value, - band: $(this).attr('id').substr(2) - }) - }, - slide: function (event, ui) { - $('#' + $(this).attr('id') + '_txt').empty().append(ui.value + 'dB'); - } - }); - } else { - $('#eq' + id).slider({ - value: value - }); - $('#eq' + id + '_txt').empty().append(Math.round(value * 100) / 100 + 'dB'); - } - }); - $('#preamp').slider('value', $('preamp', data).text()); - $('#preamp_txt').empty().append(Math.round($('preamp', data).text() * 100) / 100 + 'dB'); - }, - error: function (jqXHR, status, error) { - setTimeout(updateStatus, 500); - } - }); -} - -function updatePlayList(force_refresh) { - if (force_refresh) { - //refresh playlist.. - $('#libraryTree').jstree('refresh', -1); - } else { - //iterate through playlist.. - var match = false; - $('.jstree-leaf').each(function(){ - var id = $(this).attr('id'); - if (id != null && id.substr(0,5) == 'plid_') { - if ( id.substr(5) == current_playlist_id ) { - $(this).addClass('ui-state-highlight'); - $(this).attr('current', 'current'); - this.scrollIntoView(true); - match = true; - } else { - $(this).removeClass('ui-state-highlight'); - $(this).removeAttr('current'); - } - if ($(this).children('a').size() > 0) { - $($(this).children('a')[0]).removeClass('ui-state-active'); - } - } - }); - //local title wasn't found - refresh playlist.. - if (!match) updatePlayList(true); - } -} - -function sendCommand(params, append) { - if (current_que == 'stream') { - $.ajax({ - url: 'requests/status.xml', - data: params, - success: function (data, status, jqXHR) { - if (append != undefined) { - eval(append); - } - updateStatus(); - } - }); - } else { - if (params.plreload === false) { - $.ajax({ - url: 'requests/status.xml', - data: params, - success: function (data, status, jqXHR) { - if (append != undefined) { - eval(append); - } - } - }); - } else { - $.ajax({ - url: 'requests/status.xml', - data: params, - success: function (data, status, jqXHR) { - if (append != undefined) { - eval(append); - } - } - }); - } - } -} - -function browse(dir) { - dir = dir == undefined ? 'file://~' : dir; - $.ajax({ - url: 'requests/browse.xml', - data: 'uri=' + encodeURIComponent(dir), - success: function (data, status, jqXHR) { - var tgt = browse_target.indexOf('__') == -1 ? browse_target : browse_target.substr(0, browse_target.indexOf('__')); - $('#browse_elements').empty(); - $('element', data).each(function () { - var ext = $(this).attr('name').substr($(this).attr('name').lastIndexOf('.') + 1).toLowerCase(); - if ($(this).attr('type') == 'dir' || $.inArray(ext, video_types) != -1 || $.inArray(ext, audio_types) != -1 || $.inArray(ext, playlist_types) != -1) { - $('#browse_elements').append(createElementLi($(this).attr('name'), $(this).attr('type'), $(this).attr('uri'), ext)); - } - }); - $('[opendir]').dblclick(function () { - browse($(this).attr('opendir')); - }); - $('[openfile]').dblclick(function () { - switch (tgt) { - case '#stream_input': - $(browse_target).val($(this).attr('openfile')); - break; - case '#mosaic_open': - $('li', browse_target).remove(); - $(browse_target).append(this); - $(this).css({ - 'margin-left': -40, - 'margin-top': -46, - 'float': 'left' - }); - break; - case '#mobile': - break; - default: - sendCommand('command=in_play&input=' + encodeURIComponent($(this).attr('openfile'))); - updatePlayList(true); - break; - } - $('#window_browse').dialog('close'); - }); - $('[opendir]').click(function () { - switch (tgt) { - case '#mobile': - browse($(this).attr('opendir')); - break; - default: - break; - } - }); - $('[openfile]').click(function () { - switch (tgt) { - case '#mobile': - sendCommand('command=in_play&input=' + encodeURIComponent($(this).attr('openfile')), "window.location='mobile.html'"); - break; - default: - break; - } - }); - switch (tgt) { - case '#mobile': - break; - default: - $('[selectable]').selectable(); - break; - } - }, - error: function (jqXHR, status, error) { - setTimeout('browse("' + dir + '")', 1041); - } - }); -} - -function updateStreams() { - $.ajax({ - url: 'requests/vlm.xml', - success: function (data, status, jqXHR) { - $('#stream_info').accordion("destroy"); - $('#stream_info').empty(); - $('broadcast', data).each(function () { - var stream_div = $('#stream_status_').clone(); - var name = $(this).attr('name'); - var loop = $(this).attr('loop') == 'yes'; - var playing = $('instance', $(this)).attr('state') == 'playing'; - var file = $('input', $(this)).text(); - var output = $('output', $(this)).text(); - var time = isNaN(Math.round($('instance', $(this)).attr('time') / 1000000)) ? 0 : Math.round($('instance', $(this)).attr('time') / 1000000); - var length = isNaN(Math.round($('instance', $(this)).attr('length') / 1000000)) ? 0 : Math.round($('instance', $(this)).attr('length') / 1000000); - $('[id]', stream_div).each(function () { - $(this).attr('id', $(this).attr('id') + name); - }); - $(stream_div).attr('id', $(stream_div).attr('id') + name); - $('#stream_title_' + name, stream_div).append(name); - $('#stream_file_' + name, stream_div).append(file); - $('#stream_pos_' + name, stream_div).slider({ - value: 0, - range: "min", - min: 0, - slide: function (event, ui) { - $("#stream_current_time_" + name, stream_div).empty(); - $("#stream_current_time_" + name, stream_div).append(format_time(ui.value)); - $("#stream_total_time_" + name, stream_div).empty(); - $("#stream_total_time_" + name, stream_div).append(format_time($('#stream_pos_' + name, stream_div).slider('option', 'max'))); - sendVLMCmd('control ' + name + ' seek ' + Math.round(ui.value / $('#stream_pos_' + name, stream_div).slider('option', 'max') * 100)); - }, - change: function (event, ui) { - $("#stream_current_time_" + name, stream_div).empty(); - $("#stream_current_time_" + name, stream_div).append(format_time(ui.value)); - $("#stream_total_time_" + name, stream_div).empty(); - $("#stream_total_time_" + name, stream_div).append(format_time($('#stream_pos_' + name, stream_div).slider('option', 'max'))); - } - }); - $('#button_stream_stop_' + name, stream_div).click(function () { - sendVLMCmd('control ' + name + ' stop'); - return false; - }); - $('#button_stream_play_' + name, stream_div).click(function () { - if ($('span', this).hasClass('ui-icon-pause')) { - sendVLMCmd('control ' + name + ' pause'); - } else { - sendVLMCmd('control ' + name + ' play'); - } - }); - $('#button_stream_loop_' + name, stream_div).click(function () { - if (loop) { - sendVLMCmd('setup ' + name + ' unloop'); - } else { - sendVLMCmd('setup ' + name + ' loop'); - } - }); - $('#button_stream_delete_' + name, stream_div).click(function () { - sendVLMCmd('del ' + name); - }); - $('#stream_pos_' + name, stream_div).slider({ - max: length, - value: time - }); - if (playing) { - $('span', $('#button_stream_play_' + name, stream_div)).removeClass('ui-icon-play'); - $('span', $('#button_stream_play_' + name, stream_div)).addClass('ui-icon-pause'); - } - if (loop) { - $('#button_stream_loop_' + name, stream_div).addClass('ui-state-active'); - } - $(stream_div).css({ - 'visibility': '', - 'display': '' - }); - $('#stream_info').append(stream_div); - - }); - $('.button').hover( - - function () { - $(this).addClass('ui-state-hover'); - }, function () { - $(this).removeClass('ui-state-hover'); - }); - $('#stream_info').accordion({ - header: "h3", - collapsible: true, - autoHeight: true - }); - if (current_que == 'stream') { - $('.dynamic').empty(); - $('#mediaTitle').append($('[name="Current"] input', data).text()); - $('#totalTime').append(format_time(isNaN($('[name="Current"] instance', data).attr('length')) ? 0 : $('[name="Current"] instance', data).attr('length') / 1000000)); - $('#currentTime').append(format_time(isNaN($('[name="Current"] instance', data).attr('time')) ? 0 : $('[name="Current"] instance', data).attr('time') / 1000000)); - $('#seekSlider').slider({ - value: (($('[name="Current"] instance', data).attr('time') / 1000000) / ($('[name="Current"] instance', data).attr('length') / 1000000) * 100) - }); - $('#seekSlider').attr('totalLength', $('[name="Current"] instance', data).attr('length') / 1000000); - $('#buttonPlay').attr('state', $('[name="Current"] instance', data).length > 0 ? $('[name="Current"] instance', data).attr('state') : 'stopped'); - if ($('[name="Current"] instance', data).attr('state') == 'playing') { - $('#buttonPlay').removeClass('paused'); - $('#buttonPlay').addClass('playing'); - } else { - $('#buttonPlay').removeClass('playing'); - $('#buttonPlay').addClass('paused'); - } - setTimeout(updateStreams, 1000); - } - } - }); -} - -function updateEQ() { - $.ajax({ - url: 'requests/status.xml', - success: function (data, status, jqXHR) { - $('band', data).each(function () { - var freq = ["60 Hz","170 Hz", "310 Hz", "600 Hz", "1 kHz","3 kHz", "6 kHz", "12 kHz" , "14 kHz" , "16 kHz" ]; - var id = $(this).attr('id'); - var value = $(this).text() ? $(this).text() : 0; - if (!$('#eq_container' + id).length) { - $('#window_equalizer').append('
    ' + value + 'dB
    ' + freq[id] + '
    '); - $('#eq' + id).slider({ - min: -20, - max: 20, - step: 0.1, - range: "min", - value: value, - animate: true, - orientation: "vertical", - stop: function (event, ui) { - $('#' + $(this).attr('id') + '_txt').empty().append(ui.value + 'dB'); - sendEQCmd({ - command: 'equalizer', - val: ui.value, - band: $(this).attr('id').substr(2) - }) - }, - slide: function (event, ui) { - $('#' + $(this).attr('id') + '_txt').empty().append(ui.value + 'dB'); - } - }); - } else { - $('#eq' + id).slider({ - value: value - }); - $('#eq' + id + '_txt').empty().append(Math.round(value * 100) / 100 + 'dB'); - } - }); - $('#preamp').slider('value', $('preamp', data).text()); - $('#preamp_txt').empty().append(Math.round($('preamp', data).text() * 100) / 100 + 'dB'); - } - }) -} - -function sendVLMCmd(command, append) { - var commands = command.split(';'); - if (commands.length > 1) { - sendBatchVLMCmd(command, append); - } else { - if (current_que == 'main') { - $.ajax({ - url: 'requests/vlm_cmd.xml', - data: 'command=' + encodeURIComponent(command), - success: function (data, status, jqXHR) { - if ($('error', data).text()) { - $('#error_container').append('
    ' + $('error', data).text() + '
    '); - $('#window_error').dialog('open'); - } - if (append != undefined) { - eval(append); - } - updateStreams(); - } - }); - } else { - $.ajax({ - url: 'requests/vlm_cmd.xml', - data: 'command=' + encodeURIComponent(command), - success: function (data, status, jqXHR) { - if ($('error', data).text()) { - $('#error_container').append('
    ' + $('error', data).text() + '
    '); - $('#window_error').dialog('open'); - } - updateStreams(); - } - }); - } - } -} - -function sendBatchVLMCmd(command, append) { - var commands = command.split(';'); - $.ajax({ - url: 'requests/vlm_cmd.xml', - data: 'command=' + encodeURIComponent(commands.shift()), - success: function (data, status, jqXHR) { - if ($('error', data).text()) { - $('#error_container').append('
    ' + $('error', data).text() + '
    '); - $('#window_error').dialog('open'); - } - sendVLMCmd(commands.join(';'), append); - } - }); -} - -function sendEQCmd(params) { - $.ajax({ - url: 'requests/status.xml', - data: params, - success: function (data, status, jqXHR) { - updateEQ(); - } - }); -} - -$(function () { - $('#albumArt').load(function () { - var width = $(this).width(); - var height = $(this).height(); - var max = Math.max(width, height); - if (max > 150) { - var ratio = 150 / max; - width = Math.floor(width * ratio); - height = Math.floor(height * ratio); - } - $(this).attr('width', width).attr('height', height).css('margin-left', Math.floor((150 - width) / 2)).css('margin-top', Math.floor((150 - height) / 2)).removeClass('hidden').fadeIn(); - }); - $('#libraryTree').jstree({ - "xml_data": { - "ajax": { - "url": "requests/playlist_jstree.xml" - }, - "xsl": "nest" - }, - "themeroller": { - "item_leaf": "ui-icon-video" - }, - "core": { - "initially_open": ["plid_1", "plid_2", "plid_3"] - }, - "plugins": ["xml_data", "ui", "themeroller"] - }).bind("loaded.jstree", function (event, data) { - $('[current]', '[id^="plid_"]').each(function () { - $(this).addClass('ui-state-highlight'); - current_playlist_id = $(this).attr('id').substr(5); - }); - }).bind("refresh.jstree", function (event, data) { - $('[current]', '[id^="plid_"]').each(function () { - $(this).addClass('ui-state-highlight'); - current_playlist_id = $(this).attr('id').substr(5); - }); - }).delegate("#plid_0 li.jstree-leaf a", "click", function (event, data) { - event.preventDefault(); - current_playlist_id = $(this).parent().attr('id').substr(5); - sendCommand('command=pl_play&id=' + current_playlist_id); - }); - updateStatus(); - updateStreams(); - updateEQ(); -}); diff --git a/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/jquery.jstree.js b/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/jquery.jstree.js deleted file mode 100644 index a47e949..0000000 --- a/AudioVisualizer/bin/Debug/net6.0-windows/libvlc/win-x64/lua/http/js/jquery.jstree.js +++ /dev/null @@ -1,4544 +0,0 @@ -/* - * jsTree 1.0-rc3 - * http://jstree.com/ - * - * Copyright (c) 2010 Ivan Bozhanov (vakata.com) - * - * Licensed same as jquery - under the terms of either the MIT License or the GPL Version 2 License - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * $Date: 2011-02-09 01:17:14 +0200 (ср, 09 февр 2011) $ - * $Revision: 236 $ - */ - -/*jslint browser: true, onevar: true, undef: true, bitwise: true, strict: true */ -/*global window : false, clearInterval: false, clearTimeout: false, document: false, setInterval: false, setTimeout: false, jQuery: false, navigator: false, XSLTProcessor: false, DOMParser: false, XMLSerializer: false*/ - -"use strict"; - -// top wrapper to prevent multiple inclusion (is this OK?) -(function () { if(jQuery && jQuery.jstree) { return; } - var is_ie6 = false, is_ie7 = false, is_ff2 = false; - -/* - * jsTree core - */ -(function ($) { - // Common functions not related to jsTree - // decided to move them to a `vakata` "namespace" - $.vakata = {}; - // CSS related functions - $.vakata.css = { - get_css : function(rule_name, delete_flag, sheet) { - rule_name = rule_name.toLowerCase(); - var css_rules = sheet.cssRules || sheet.rules, - j = 0; - do { - if(css_rules.length && j > css_rules.length + 5) { return false; } - if(css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) { - if(delete_flag === true) { - if(sheet.removeRule) { sheet.removeRule(j); } - if(sheet.deleteRule) { sheet.deleteRule(j); } - return true; - } - else { return css_rules[j]; } - } - } - while (css_rules[++j]); - return false; - }, - add_css : function(rule_name, sheet) { - if($.jstree.css.get_css(rule_name, false, sheet)) { return false; } - if(sheet.insertRule) { sheet.insertRule(rule_name + ' { }', 0); } else { sheet.addRule(rule_name, null, 0); } - return $.vakata.css.get_css(rule_name); - }, - remove_css : function(rule_name, sheet) { - return $.vakata.css.get_css(rule_name, true, sheet); - }, - add_sheet : function(opts) { - var tmp = false, is_new = true; - if(opts.str) { - if(opts.title) { tmp = $("style[id='" + opts.title + "-stylesheet']")[0]; } - if(tmp) { is_new = false; } - else { - tmp = document.createElement("style"); - tmp.setAttribute('type',"text/css"); - if(opts.title) { tmp.setAttribute("id", opts.title + "-stylesheet"); } - } - if(tmp.styleSheet) { - if(is_new) { - document.getElementsByTagName("head")[0].appendChild(tmp); - tmp.styleSheet.cssText = opts.str; - } - else { - tmp.styleSheet.cssText = tmp.styleSheet.cssText + " " + opts.str; - } - } - else { - tmp.appendChild(document.createTextNode(opts.str)); - document.getElementsByTagName("head")[0].appendChild(tmp); - } - return tmp.sheet || tmp.styleSheet; - } - if(opts.url) { - if(document.createStyleSheet) { - try { tmp = document.createStyleSheet(opts.url); } catch (e) { } - } - else { - tmp = document.createElement('link'); - tmp.rel = 'stylesheet'; - tmp.type = 'text/css'; - tmp.media = "all"; - tmp.href = opts.url; - document.getElementsByTagName("head")[0].appendChild(tmp); - return tmp.styleSheet; - } - } - } - }; - - // private variables - var instances = [], // instance array (used by $.jstree.reference/create/focused) - focused_instance = -1, // the index in the instance array of the currently focused instance - plugins = {}, // list of included plugins - prepared_move = {}; // for the move_node function - - // jQuery plugin wrapper (thanks to jquery UI widget function) - $.fn.jstree = function (settings) { - var isMethodCall = (typeof settings == 'string'), // is this a method call like $().jstree("open_node") - args = Array.prototype.slice.call(arguments, 1), - returnValue = this; - - // if a method call execute the method on all selected instances - if(isMethodCall) { - if(settings.substring(0, 1) == '_') { return returnValue; } - this.each(function() { - var instance = instances[$.data(this, "jstree-instance-id")], - methodValue = (instance && $.isFunction(instance[settings])) ? instance[settings].apply(instance, args) : instance; - if(typeof methodValue !== "undefined" && (settings.indexOf("is_") === 0 || (methodValue !== true && methodValue !== false))) { returnValue = methodValue; return false; } - }); - } - else { - this.each(function() { - // extend settings and allow for multiple hashes and $.data - var instance_id = $.data(this, "jstree-instance-id"), - a = [], - b = settings ? $.extend({}, true, settings) : {}, - c = $(this), - s = false, - t = []; - a = a.concat(args); - if(c.data("jstree")) { a.push(c.data("jstree")); } - b = a.length ? $.extend.apply(null, [true, b].concat(a)) : b; - - // if an instance already exists, destroy it first - if(typeof instance_id !== "undefined" && instances[instance_id]) { instances[instance_id].destroy(); } - // push a new empty object to the instances array - instance_id = parseInt(instances.push({}),10) - 1; - // store the jstree instance id to the container element - $.data(this, "jstree-instance-id", instance_id); - // clean up all plugins - b.plugins = $.isArray(b.plugins) ? b.plugins : $.jstree.defaults.plugins.slice(); - b.plugins.unshift("core"); - // only unique plugins - b.plugins = b.plugins.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(","); - - // extend defaults with passed data - s = $.extend(true, {}, $.jstree.defaults, b); - s.plugins = b.plugins; - $.each(plugins, function (i, val) { - if($.inArray(i, s.plugins) === -1) { s[i] = null; delete s[i]; } - else { t.push(i); } - }); - s.plugins = t; - - // push the new object to the instances array (at the same time set the default classes to the container) and init - instances[instance_id] = new $.jstree._instance(instance_id, $(this).addClass("jstree jstree-" + instance_id), s); - // init all activated plugins for this instance - $.each(instances[instance_id]._get_settings().plugins, function (i, val) { instances[instance_id].data[val] = {}; }); - $.each(instances[instance_id]._get_settings().plugins, function (i, val) { if(plugins[val]) { plugins[val].__init.apply(instances[instance_id]); } }); - // initialize the instance - setTimeout(function() { instances[instance_id].init(); }, 0); - }); - } - // return the jquery selection (or if it was a method call that returned a value - the returned value) - return returnValue; - }; - // object to store exposed functions and objects - $.jstree = { - defaults : { - plugins : [] - }, - _focused : function () { return instances[focused_instance] || null; }, - _reference : function (needle) { - // get by instance id - if(instances[needle]) { return instances[needle]; } - // get by DOM (if still no luck - return null - var o = $(needle); - if(!o.length && typeof needle === "string") { o = $("#" + needle); } - if(!o.length) { return null; } - return instances[o.closest(".jstree").data("jstree-instance-id")] || null; - }, - _instance : function (index, container, settings) { - // for plugins to store data in - this.data = { core : {} }; - this.get_settings = function () { return $.extend(true, {}, settings); }; - this._get_settings = function () { return settings; }; - this.get_index = function () { return index; }; - this.get_container = function () { return container; }; - this.get_container_ul = function () { return container.children("ul:eq(0)"); }; - this._set_settings = function (s) { - settings = $.extend(true, {}, settings, s); - }; - }, - _fn : { }, - plugin : function (pname, pdata) { - pdata = $.extend({}, { - __init : $.noop, - __destroy : $.noop, - _fn : {}, - defaults : false - }, pdata); - plugins[pname] = pdata; - - $.jstree.defaults[pname] = pdata.defaults; - $.each(pdata._fn, function (i, val) { - val.plugin = pname; - val.old = $.jstree._fn[i]; - $.jstree._fn[i] = function () { - var rslt, - func = val, - args = Array.prototype.slice.call(arguments), - evnt = new $.Event("before.jstree"), - rlbk = false; - - if(this.data.core.locked === true && i !== "unlock" && i !== "is_locked") { return; } - - // Check if function belongs to the included plugins of this instance - do { - if(func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) { break; } - func = func.old; - } while(func); - if(!func) { return; } - - // context and function to trigger events, then finally call the function - if(i.indexOf("_") === 0) { - rslt = func.apply(this, args); - } - else { - rslt = this.get_container().triggerHandler(evnt, { "func" : i, "inst" : this, "args" : args, "plugin" : func.plugin }); - if(rslt === false) { return; } - if(typeof rslt !== "undefined") { args = rslt; } - - rslt = func.apply( - $.extend({}, this, { - __callback : function (data) { - this.get_container().triggerHandler( i + '.jstree', { "inst" : this, "args" : args, "rslt" : data, "rlbk" : rlbk }); - }, - __rollback : function () { - rlbk = this.get_rollback(); - return rlbk; - }, - __call_old : function (replace_arguments) { - return func.old.apply(this, (replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ) ); - } - }), args); - } - - // return the result - return rslt; - }; - $.jstree._fn[i].old = val.old; - $.jstree._fn[i].plugin = pname; - }); - }, - rollback : function (rb) { - if(rb) { - if(!$.isArray(rb)) { rb = [ rb ]; } - $.each(rb, function (i, val) { - instances[val.i].set_rollback(val.h, val.d); - }); - } - } - }; - // set the prototype for all instances - $.jstree._fn = $.jstree._instance.prototype = {}; - - // load the css when DOM is ready - $(function() { - // code is copied from jQuery ($.browser is deprecated + there is a bug in IE) - var u = navigator.userAgent.toLowerCase(), - v = (u.match( /.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1], - css_string = '' + - '.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } ' + - '.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } ' + - '.jstree-rtl li { margin-left:0; margin-right:18px; } ' + - '.jstree > ul > li { margin-left:0px; } ' + - '.jstree-rtl > ul > li { margin-right:0px; } ' + - '.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } ' + - '.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } ' + - '.jstree a:focus { outline: none; } ' + - '.jstree a > ins { height:16px; width:16px; } ' + - '.jstree a > .jstree-icon { margin-right:3px; } ' + - '.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; } ' + - 'li.jstree-open > ul { display:block; } ' + - 'li.jstree-closed > ul { display:none; } '; - // Correct IE 6 (does not support the > CSS selector) - if(/msie/.test(u) && parseInt(v, 10) == 6) { - is_ie6 = true; - - // fix image flicker and lack of caching - try { - document.execCommand("BackgroundImageCache", false, true); - } catch (err) { } - - css_string += '' + - '.jstree li { height:18px; margin-left:0; margin-right:0; } ' + - '.jstree li li { margin-left:18px; } ' + - '.jstree-rtl li li { margin-left:0px; margin-right:18px; } ' + - 'li.jstree-open ul { display:block; } ' + - 'li.jstree-closed ul { display:none !important; } ' + - '.jstree li a { display:inline; border-width:0 !important; padding:0px 2px !important; } ' + - '.jstree li a ins { height:16px; width:16px; margin-right:3px; } ' + - '.jstree-rtl li a ins { margin-right:0px; margin-left:3px; } '; - } - // Correct IE 7 (shifts anchor nodes onhover) - if(/msie/.test(u) && parseInt(v, 10) == 7) { - is_ie7 = true; - css_string += '.jstree li a { border-width:0 !important; padding:0px 2px !important; } '; - } - // correct ff2 lack of display:inline-block - if(!/compatible/.test(u) && /mozilla/.test(u) && parseFloat(v, 10) < 1.9) { - is_ff2 = true; - css_string += '' + - '.jstree ins { display:-moz-inline-box; } ' + - '.jstree li { line-height:12px; } ' + // WHY?? - '.jstree a { display:-moz-inline-box; } ' + - '.jstree .jstree-no-icons .jstree-checkbox { display:-moz-inline-stack !important; } '; - /* this shouldn't be here as it is theme specific */ - } - // the default stylesheet - $.vakata.css.add_sheet({ str : css_string, title : "jstree" }); - }); - - // core functions (open, close, create, update, delete) - $.jstree.plugin("core", { - __init : function () { - this.data.core.locked = false; - this.data.core.to_open = this.get_settings().core.initially_open; - this.data.core.to_load = this.get_settings().core.initially_load; - }, - defaults : { - html_titles : false, - animation : 500, - initially_open : [], - initially_load : [], - open_parents : true, - notify_plugins : true, - rtl : false, - load_open : false, - strings : { - loading : "Loading ...", - new_node : "New node", - multiple_selection : "Multiple selection" - } - }, - _fn : { - init : function () { - this.set_focus(); - if(this._get_settings().core.rtl) { - this.get_container().addClass("jstree-rtl").css("direction", "rtl"); - } - this.get_container().html(""); - this.data.core.li_height = this.get_container_ul().find("li.jstree-closed, li.jstree-leaf").eq(0).height() || 18; - - this.get_container() - .delegate("li > ins", "click.jstree", $.proxy(function (event) { - var trgt = $(event.target); - if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); } - }, this)) - .bind("mousedown.jstree", $.proxy(function () { - this.set_focus(); // This used to be setTimeout(set_focus,0) - why? - }, this)) - .bind("dblclick.jstree", function (event) { - var sel; - if(document.selection && document.selection.empty) { document.selection.empty(); } - else { - if(window.getSelection) { - sel = window.getSelection(); - try { - sel.removeAllRanges(); - sel.collapse(); - } catch (err) { } - } - } - }); - if(this._get_settings().core.notify_plugins) { - this.get_container() - .bind("load_node.jstree", $.proxy(function (e, data) { - var o = this._get_node(data.rslt.obj), - t = this; - if(o === -1) { o = this.get_container_ul(); } - if(!o.length) { return; } - o.find("li").each(function () { - var th = $(this); - if(th.data("jstree")) { - $.each(th.data("jstree"), function (plugin, values) { - if(t.data[plugin] && $.isFunction(t["_" + plugin + "_notify"])) { - t["_" + plugin + "_notify"].call(t, th, values); - } - }); - } - }); - }, this)); - } - if(this._get_settings().core.load_open) { - this.get_container() - .bind("load_node.jstree", $.proxy(function (e, data) { - var o = this._get_node(data.rslt.obj), - t = this; - if(o === -1) { o = this.get_container_ul(); } - if(!o.length) { return; } - o.find("li.jstree-open:not(:has(ul))").each(function () { - t.load_node(this, $.noop, $.noop); - }); - }, this)); - } - this.__callback(); - this.load_node(-1, function () { this.loaded(); this.reload_nodes(); }); - }, - destroy : function () { - var i, - n = this.get_index(), - s = this._get_settings(), - _this = this; - - $.each(s.plugins, function (i, val) { - try { plugins[val].__destroy.apply(_this); } catch(err) { } - }); - this.__callback(); - // set focus to another instance if this one is focused - if(this.is_focused()) { - for(i in instances) { - if(instances.hasOwnProperty(i) && i != n) { - instances[i].set_focus(); - break; - } - } - } - // if no other instance found - if(n === focused_instance) { focused_instance = -1; } - // remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events - this.get_container() - .unbind(".jstree") - .undelegate(".jstree") - .removeData("jstree-instance-id") - .find("[class^='jstree']") - .andSelf() - .attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); }); - $(document) - .unbind(".jstree-" + n) - .undelegate(".jstree-" + n); - // remove the actual data - instances[n] = null; - delete instances[n]; - }, - - _core_notify : function (n, data) { - if(data.opened) { - this.open_node(n, false, true); - } - }, - - lock : function () { - this.data.core.locked = true; - this.get_container().children("ul").addClass("jstree-locked").css("opacity","0.7"); - this.__callback({}); - }, - unlock : function () { - this.data.core.locked = false; - this.get_container().children("ul").removeClass("jstree-locked").css("opacity","1"); - this.__callback({}); - }, - is_locked : function () { return this.data.core.locked; }, - save_opened : function () { - var _this = this; - this.data.core.to_open = []; - this.get_container_ul().find("li.jstree-open").each(function () { - if(this.id) { _this.data.core.to_open.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); } - }); - this.__callback(_this.data.core.to_open); - }, - save_loaded : function () { }, - reload_nodes : function (is_callback) { - var _this = this, - done = true, - current = [], - remaining = []; - if(!is_callback) { - this.data.core.reopen = false; - this.data.core.refreshing = true; - this.data.core.to_open = $.map($.makeArray(this.data.core.to_open), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); }); - this.data.core.to_load = $.map($.makeArray(this.data.core.to_load), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); }); - if(this.data.core.to_open.length) { - this.data.core.to_load = this.data.core.to_load.concat(this.data.core.to_open); - } - } - if(this.data.core.to_load.length) { - $.each(this.data.core.to_load, function (i, val) { - if(val == "#") { return true; } - if($(val).length) { current.push(val); } - else { remaining.push(val); } - }); - if(current.length) { - this.data.core.to_load = remaining; - $.each(current, function (i, val) { - if(!_this._is_loaded(val)) { - _this.load_node(val, function () { _this.reload_nodes(true); }, function () { _this.reload_nodes(true); }); - done = false; - } - }); - } - } - if(this.data.core.to_open.length) { - $.each(this.data.core.to_open, function (i, val) { - _this.open_node(val, false, true); - }); - } - if(done) { - // TODO: find a more elegant approach to synchronizing returning requests - if(this.data.core.reopen) { clearTimeout(this.data.core.reopen); } - this.data.core.reopen = setTimeout(function () { _this.__callback({}, _this); }, 50); - this.data.core.refreshing = false; - this.reopen(); - } - }, - reopen : function () { - var _this = this; - if(this.data.core.to_open.length) { - $.each(this.data.core.to_open, function (i, val) { - _this.open_node(val, false, true); - }); - } - this.__callback({}); - }, - refresh : function (obj) { - var _this = this; - this.save_opened(); - if(!obj) { obj = -1; } - obj = this._get_node(obj); - if(!obj) { obj = -1; } - if(obj !== -1) { obj.children("UL").remove(); } - else { this.get_container_ul().empty(); } - this.load_node(obj, function () { _this.__callback({ "obj" : obj}); _this.reload_nodes(); }); - }, - // Dummy function to fire after the first load (so that there is a jstree.loaded event) - loaded : function () { - this.__callback(); - }, - // deal with focus - set_focus : function () { - if(this.is_focused()) { return; } - var f = $.jstree._focused(); - if(f) { f.unset_focus(); } - - this.get_container().addClass("jstree-focused"); - focused_instance = this.get_index(); - this.__callback(); - }, - is_focused : function () { - return focused_instance == this.get_index(); - }, - unset_focus : function () { - if(this.is_focused()) { - this.get_container().removeClass("jstree-focused"); - focused_instance = -1; - } - this.__callback(); - }, - - // traverse - _get_node : function (obj) { - var $obj = $(obj, this.get_container()); - if($obj.is(".jstree") || obj == -1) { return -1; } - $obj = $obj.closest("li", this.get_container()); - return $obj.length ? $obj : false; - }, - _get_next : function (obj, strict) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().find("> ul > li:first-child"); } - if(!obj.length) { return false; } - if(strict) { return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; } - - if(obj.hasClass("jstree-open")) { return obj.find("li:eq(0)"); } - else if(obj.nextAll("li").size() > 0) { return obj.nextAll("li:eq(0)"); } - else { return obj.parentsUntil(".jstree","li").next("li").eq(0); } - }, - _get_prev : function (obj, strict) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().find("> ul > li:last-child"); } - if(!obj.length) { return false; } - if(strict) { return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false; } - - if(obj.prev("li").length) { - obj = obj.prev("li").eq(0); - while(obj.hasClass("jstree-open")) { obj = obj.children("ul:eq(0)").children("li:last"); } - return obj; - } - else { var o = obj.parentsUntil(".jstree","li:eq(0)"); return o.length ? o : false; } - }, - _get_parent : function (obj) { - obj = this._get_node(obj); - if(obj == -1 || !obj.length) { return false; } - var o = obj.parentsUntil(".jstree", "li:eq(0)"); - return o.length ? o : -1; - }, - _get_children : function (obj) { - obj = this._get_node(obj); - if(obj === -1) { return this.get_container().children("ul:eq(0)").children("li"); } - if(!obj.length) { return false; } - return obj.children("ul:eq(0)").children("li"); - }, - get_path : function (obj, id_mode) { - var p = [], - _this = this; - obj = this._get_node(obj); - if(obj === -1 || !obj || !obj.length) { return false; } - obj.parentsUntil(".jstree", "li").each(function () { - p.push( id_mode ? this.id : _this.get_text(this) ); - }); - p.reverse(); - p.push( id_mode ? obj.attr("id") : this.get_text(obj) ); - return p; - }, - - // string functions - _get_string : function (key) { - return this._get_settings().core.strings[key] || key; - }, - - is_open : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-open"); }, - is_closed : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-closed"); }, - is_leaf : function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-leaf"); }, - correct_state : function (obj) { - obj = this._get_node(obj); - if(!obj || obj === -1) { return false; } - obj.removeClass("jstree-closed jstree-open").addClass("jstree-leaf").children("ul").remove(); - this.__callback({ "obj" : obj }); - }, - // open/close - open_node : function (obj, callback, skip_animation) { - obj = this._get_node(obj); - if(!obj.length) { return false; } - if(!obj.hasClass("jstree-closed")) { if(callback) { callback.call(); } return false; } - var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation, - t = this; - if(!this._is_loaded(obj)) { - obj.children("a").addClass("jstree-loading"); - this.load_node(obj, function () { t.open_node(obj, callback, skip_animation); }, callback); - } - else { - if(this._get_settings().core.open_parents) { - obj.parentsUntil(".jstree",".jstree-closed").each(function () { - t.open_node(this, false, true); - }); - } - if(s) { obj.children("ul").css("display","none"); } - obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading"); - if(s) { obj.children("ul").stop(true, true).slideDown(s, function () { this.style.display = ""; t.after_open(obj); }); } - else { t.after_open(obj); } - this.__callback({ "obj" : obj }); - if(callback) { callback.call(); } - } - }, - after_open : function (obj) { this.__callback({ "obj" : obj }); }, - close_node : function (obj, skip_animation) { - obj = this._get_node(obj); - var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation, - t = this; - if(!obj.length || !obj.hasClass("jstree-open")) { return false; } - if(s) { obj.children("ul").attr("style","display:block !important"); } - obj.removeClass("jstree-open").addClass("jstree-closed"); - if(s) { obj.children("ul").stop(true, true).slideUp(s, function () { this.style.display = ""; t.after_close(obj); }); } - else { t.after_close(obj); } - this.__callback({ "obj" : obj }); - }, - after_close : function (obj) { this.__callback({ "obj" : obj }); }, - toggle_node : function (obj) { - obj = this._get_node(obj); - if(obj.hasClass("jstree-closed")) { return this.open_node(obj); } - if(obj.hasClass("jstree-open")) { return this.close_node(obj); } - }, - open_all : function (obj, do_animation, original_obj) { - obj = obj ? this._get_node(obj) : -1; - if(!obj || obj === -1) { obj = this.get_container_ul(); } - if(original_obj) { - obj = obj.find("li.jstree-closed"); - } - else { - original_obj = obj; - if(obj.is(".jstree-closed")) { obj = obj.find("li.jstree-closed").andSelf(); } - else { obj = obj.find("li.jstree-closed"); } - } - var _this = this; - obj.each(function () { - var __this = this; - if(!_this._is_loaded(this)) { _this.open_node(this, function() { _this.open_all(__this, do_animation, original_obj); }, !do_animation); } - else { _this.open_node(this, false, !do_animation); } - }); - // so that callback is fired AFTER all nodes are open - if(original_obj.find('li.jstree-closed').length === 0) { this.__callback({ "obj" : original_obj }); } - }, - close_all : function (obj, do_animation) { - var _this = this; - obj = obj ? this._get_node(obj) : this.get_container(); - if(!obj || obj === -1) { obj = this.get_container_ul(); } - obj.find("li.jstree-open").andSelf().each(function () { _this.close_node(this, !do_animation); }); - this.__callback({ "obj" : obj }); - }, - clean_node : function (obj) { - obj = obj && obj != -1 ? $(obj) : this.get_container_ul(); - obj = obj.is("li") ? obj.find("li").andSelf() : obj.find("li"); - obj.removeClass("jstree-last") - .filter("li:last-child").addClass("jstree-last").end() - .filter(":has(li)") - .not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed"); - obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove(); - this.__callback({ "obj" : obj }); - }, - // rollback - get_rollback : function () { - this.__callback(); - return { i : this.get_index(), h : this.get_container().children("ul").clone(true), d : this.data }; - }, - set_rollback : function (html, data) { - this.get_container().empty().append(html); - this.data = data; - this.__callback(); - }, - // Dummy functions to be overwritten by any datastore plugin included - load_node : function (obj, s_call, e_call) { this.__callback({ "obj" : obj }); }, - _is_loaded : function (obj) { return true; }, - - // Basic operations: create - create_node : function (obj, position, js, callback, is_loaded) { - obj = this._get_node(obj); - position = typeof position === "undefined" ? "last" : position; - var d = $("
  • "), - s = this._get_settings().core, - tmp; - - if(obj !== -1 && !obj.length) { return false; } - if(!is_loaded && !this._is_loaded(obj)) { this.load_node(obj, function () { this.create_node(obj, position, js, callback, true); }); return false; } - - this.__rollback(); - - if(typeof js === "string") { js = { "data" : js }; } - if(!js) { js = {}; } - if(js.attr) { d.attr(js.attr); } - if(js.metadata) { d.data(js.metadata); } - if(js.state) { d.addClass("jstree-" + js.state); } - if(!js.data) { js.data = this._get_string("new_node"); } - if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); } - $.each(js.data, function (i, m) { - tmp = $(""); - if($.isFunction(m)) { m = m.call(this, js); } - if(typeof m == "string") { tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m); } - else { - if(!m.attr) { m.attr = {}; } - if(!m.attr.href) { m.attr.href = '#'; } - tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title); - if(m.language) { tmp.addClass(m.language); } - } - tmp.prepend(" "); - if(m.icon) { - if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); } - else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); } - } - d.append(tmp); - }); - d.prepend(" "); - if(obj === -1) { - obj = this.get_container(); - if(position === "before") { position = "first"; } - if(position === "after") { position = "last"; } - } - switch(position) { - case "before": obj.before(d); tmp = this._get_parent(obj); break; - case "after" : obj.after(d); tmp = this._get_parent(obj); break; - case "inside": - case "first" : - if(!obj.children("ul").length) { obj.append("