From 1a1c8e71fcd14858f595029f089b2d4a00202b32 Mon Sep 17 00:00:00 2001
From: ogi <Administrator@S-OGI-PC>
Date: Fri, 05 Dec 2025 09:24:16 +0900
Subject: [PATCH] プロジェクトファイルを追加。
---
HotelPms.Share.Windows/Component/TabMark.cs | 727 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 727 insertions(+), 0 deletions(-)
diff --git a/HotelPms.Share.Windows/Component/TabMark.cs b/HotelPms.Share.Windows/Component/TabMark.cs
new file mode 100644
index 0000000..1c5c54d
--- /dev/null
+++ b/HotelPms.Share.Windows/Component/TabMark.cs
@@ -0,0 +1,727 @@
+using HotelPms.Share.Windows.GraphicsApi;
+using HotelPms.Share.Windows.Util;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Windows.Forms;
+using System.Windows.Forms.VisualStyles;
+
+namespace HotelPms.Share.Windows.Component
+{
+ public class TabMark : System.Windows.Forms.Control
+ {
+ #region ★★★★★ Declartions ★★★★★
+
+ public enum HitDownSquare : int
+ {
+ None = 0,
+ Left,
+ Right,
+ Tab,
+ }
+
+ public enum AngleStyle : int
+ {
+ /// <summary>
+ /// 直角
+ /// </summary>
+ Normal = 0,
+ /// <summary>
+ /// 円角
+ /// </summary>
+ Round = 1
+ }
+
+ public sealed class HitTestInfo
+ {
+ public static readonly HitTestInfo Nowhere;
+
+ internal int y;
+ internal int x;
+ internal int index = 0;
+ internal HitDownSquare type;
+
+ static HitTestInfo()
+ {
+ HitTestInfo.Nowhere = new HitTestInfo();
+ }
+
+ internal HitTestInfo()
+ {
+ this.type = HitDownSquare.None;
+ this.x = -1;
+ this.y = -1;
+ }
+
+ public int Index { get { return index; } }
+
+ public int X
+ {
+ get { return this.x; }
+ }
+
+ public int Y
+ {
+ get { return this.y; }
+ }
+
+ public HitDownSquare Type
+ {
+ get { return this.type; }
+ }
+
+ public override int GetHashCode()
+ {
+ return this.x << 16 | this.y << 8 | (int)this.type;
+ }
+
+ public override bool Equals(object obj)
+ {
+ HitTestInfo hi = obj as HitTestInfo;
+ return (this.type == hi.type && this.x == hi.x && this.x == hi.x);
+ }
+
+ public override string ToString()
+ {
+ return string.Concat(new string[7] { "{ ", this.type.ToString(), ",", this.x.ToString(), ",", this.y.ToString(), "}" });
+ }
+ }
+
+
+ public event CellFormatEventHandler CellFormat = null;
+ public event EventHandler SelectedIndexChanged = null;
+
+ private Rectangle m_LeftRect;
+ private Rectangle m_RightRect;
+ private List<Rectangle> m_TabRect = new List<Rectangle>();
+ private StringFormat m_StringFormat = new StringFormat(StringFormatFlags.NoWrap);
+ private int m_TabCount = 3;
+ private int m_FocusIndex = -1;
+ private bool m_IsMouseDown = false;
+
+ #endregion
+
+ #region ★★★★★ Property ★★★★★
+
+ //private int m_LastMouseClickIndex = -1;
+ //public int LastMouseClickIndex
+ //{
+ // get { return m_LastMouseClickIndex; }
+ // set { m_LastMouseClickIndex = value; }
+ //}
+
+ private bool m_FirstColFrozen = false;
+ public bool FirstColFrozen
+ {
+ get { return m_FirstColFrozen; }
+ set { m_FirstColFrozen = value; }
+ }
+
+ private Color m_SelectBorderColor = Color.FromArgb(255, 86, 157, 229);
+
+ public Color SelectBorderColor
+ {
+ get { return m_SelectBorderColor; }
+ set { m_SelectBorderColor = value; }
+ }
+
+ private Color m_FocusBackColorMax = Color.FromArgb(255, 228, 240, 252);
+
+ public Color FocusBackColorMax
+ {
+ get { return m_FocusBackColorMax; }
+ set { m_FocusBackColorMax = value; }
+ }
+ private Color m_FocusBackColorMin = Color.FromArgb(255, 234, 243, 252);
+
+ public Color FocusBackColorMin
+ {
+ get { return m_FocusBackColorMin; }
+ set { m_FocusBackColorMin = value; }
+ }
+
+ private Color m_MouseDownBackColorMax = Color.FromArgb(255, 207, 230, 252);
+
+ public Color MouseDownBackColorMax
+ {
+ get { return m_MouseDownBackColorMax; }
+ set { m_MouseDownBackColorMax = value; }
+ }
+ private Color m_MouseDownBackColorMin = Color.FromArgb(255, 217, 235, 252);
+
+ public Color MouseDownBackColorMin
+ {
+ get { return m_MouseDownBackColorMin; }
+ set { m_MouseDownBackColorMin = value; }
+ }
+
+ /// <summary>
+ /// 表示タブの数
+ /// </summary>
+ public int TabCount
+ {
+ get { return m_TabCount; }
+ set { m_TabCount = value; Invalidate(); }
+ }
+
+ private int m_ScrollButtonWidth = 35;
+
+ /// <summary>
+ /// 浸り右
+ /// </summary>
+ public int ScrollButtonWidth
+ {
+ get { return m_ScrollButtonWidth; }
+ set { m_ScrollButtonWidth = value; Invalidate(); }
+ }
+
+ private Color m_ScrollButtonBackColorMax = Color.FromArgb(255, 86, 157, 229);
+
+ public Color ScrollButtonBackColorMax
+ {
+ get { return m_ScrollButtonBackColorMax; }
+ set { m_ScrollButtonBackColorMax = value; Invalidate(); }
+ }
+
+ private Color m_ScrollButtonBackColorMin = Color.FromArgb(255, 229, 240, 251);
+
+ public Color ScrollButtonBackColorMin
+ {
+ get { return m_ScrollButtonBackColorMin; }
+ set { m_ScrollButtonBackColorMin = value; Invalidate(); }
+ }
+
+ private Color m_ScrollButtonForeColor = Color.Black;
+
+ public Color ScrollButtonForeColor
+ {
+ get { return m_ScrollButtonForeColor; }
+ set { m_ScrollButtonForeColor = value; Invalidate(); }
+ }
+ private Image m_ScrollButtonImage = null;
+
+ public Image ScrollButtonImage
+ {
+ get { return m_ScrollButtonImage; }
+ set { m_ScrollButtonImage = value; Invalidate(); }
+ }
+
+ private string m_ScrollLeftText = "<<";
+
+ public string ScrollLeftText
+ {
+ get { return m_ScrollLeftText; }
+ set { m_ScrollLeftText = value; Invalidate(); }
+ }
+
+ private string m_ScrollRightText = ">>";
+
+ public string ScrollRightText
+ {
+ get { return m_ScrollRightText; }
+ set { m_ScrollRightText = value; Invalidate(); }
+ }
+
+ private List<string> m_TabKeyList = new List<string>();
+
+ //public List<string> TabKeyList
+ //{
+ // get { return m_TabKeyList; }
+ // set { m_TabKeyList = value; }
+ //}
+ private List<string> m_TabTextList = new List<string>();
+
+ //public List<string> TabTextList
+ //{
+ // get { return m_TabTextList; }
+ // set { m_TabTextList = value; }
+ //}
+ private int m_SelectIndex = 0;
+
+ public int SelectIndex
+ {
+ get { return m_SelectIndex; }
+ set
+ {
+ m_SelectIndex = value;
+ if(m_TabKeyList.Count > 0 && m_TabCount > 0)
+ {
+ if (m_SelectIndex < m_TopIndex) { m_TopIndex = (m_SelectIndex - 1 < 0) ? 0 : (m_SelectIndex - 1); }
+ if ((m_TopIndex + m_TabCount - 1) < m_SelectIndex)
+ {
+ m_TopIndex = m_SelectIndex - m_TabCount + 2;
+ if ((m_TopIndex + m_TabCount - 1) > (m_TabKeyList.Count - 1)) { m_TopIndex = m_SelectIndex - m_TabCount + 1; }
+ }
+ if ((m_TopIndex + m_TabCount - 1) == m_SelectIndex && m_SelectIndex < (m_TabKeyList.Count - 1)) { m_TopIndex++; }
+ }
+ Invalidate();
+ }
+ }
+
+ public string SelectKey
+ {
+ get { return m_TabKeyList.Count == 0 ? string.Empty : m_TabKeyList[m_SelectIndex]; }
+ }
+
+ public int DataCount
+ {
+ get { return m_TabKeyList.Count; }
+ }
+
+ private Color m_SelectBackColorMin = SystemColors.Control;
+
+ public Color SelectBackColorMin
+ {
+ get { return m_SelectBackColorMin; }
+ set { m_SelectBackColorMin = value; Invalidate(); }
+ }
+
+ private Color m_SelectBackColorMax = SystemColors.Highlight;
+
+ public Color SelectBackColorMax
+ {
+ get { return m_SelectBackColorMax; }
+ set { m_SelectBackColorMax = value; Invalidate(); }
+ }
+ private Color m_SelectForeColor = SystemColors.HighlightText;
+
+ public Color SelectForeColor
+ {
+ get { return m_SelectForeColor; }
+ set { m_SelectForeColor = value; Invalidate(); }
+ }
+ private int m_TabSpace = 2;
+
+ public int TabSpace
+ {
+ get { return m_TabSpace; }
+ set { m_TabSpace = value; Invalidate(); }
+ }
+ private Color m_TabBackColorMax = Color.FromArgb(255, 86, 157, 229);
+
+ public Color TabBackColorMax
+ {
+ get { return m_TabBackColorMax; }
+ set { m_TabBackColorMax = value; Invalidate(); }
+ }
+
+ private Color m_TabBackColorMin = Color.FromArgb(255, 229, 240, 251);
+
+ public Color TabBackColorMin
+ {
+ get { return m_TabBackColorMin; }
+ set { m_TabBackColorMin = value; Invalidate(); }
+ }
+
+ private Color m_TabForeColor = Color.Black;
+
+ public Color TabForeColor
+ {
+ get { return m_TabForeColor; }
+ set { m_TabForeColor = value; Invalidate(); }
+ }
+
+ private Color m_BorderColor = VisualStyleInformation.TextControlBorder;
+
+ public Color BorderColor
+ {
+ get { return m_BorderColor; }
+ set { m_BorderColor = value; Invalidate(); }
+ }
+ private AngleStyle m_TabStyle = AngleStyle.Normal; //0.直 1.円
+
+ public AngleStyle TabStyle
+ {
+ get { return m_TabStyle; }
+ set { m_TabStyle = value; Invalidate(); }
+ }
+
+ private int m_CornerRadius = 10;
+
+ public int CornerRadius
+ {
+ get { return m_CornerRadius; }
+ set { m_CornerRadius = value; Invalidate(); }
+ }
+
+ private int m_TopIndex = 0;
+
+ public int TopIndex
+ {
+ get { return m_TopIndex; }
+ set { m_TopIndex = value; Invalidate(); }
+ }
+
+ #endregion
+
+ #region ★★★★★ Class Event ★★★★★
+
+ public TabMark()
+ {
+ SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.Opaque | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.Selectable | ControlStyles.UserMouse, true);
+ m_StringFormat.Alignment = StringAlignment.Center;
+ m_StringFormat.LineAlignment = StringAlignment.Center;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ m_StringFormat.Dispose();
+ base.Dispose(disposing);
+ }
+
+ #endregion
+
+ #region ★★★★★ Control Event ★★★★★
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ base.OnPaint(e);
+
+ try
+ {
+ GdiPlus.FillRectangle(e.Graphics, BackColor, ClientRectangle);
+
+ if (m_TabCount <= 0 || m_TabKeyList.Count == 0 || m_TabTextList.Count == 0)
+ {
+ return;
+ }
+
+ //余白取る
+ Rectangle allRect = new Rectangle(ClientRectangle.X + this.Margin.Left, ClientRectangle.Y + this.Margin.Top,
+ ClientRectangle.Width - this.Margin.Left - this.Margin.Right, ClientRectangle.Height - this.Margin.Top - this.Margin.Bottom);
+
+ //描く領域の確定
+ bool showScroll = (m_TabKeyList.Count > m_TabCount);
+ if (showScroll)
+ {
+ m_RightRect = new Rectangle(ClientRectangle.Width - (m_ScrollButtonWidth + this.Margin.Right), allRect.Y, m_ScrollButtonWidth, allRect.Height);
+ DrawCell(e.Graphics, m_RightRect, m_ScrollRightText, m_ScrollButtonBackColorMin, m_ScrollButtonBackColorMax, m_ScrollButtonForeColor, m_ScrollButtonForeColor, false, m_FocusIndex == -3);
+
+ m_LeftRect = new Rectangle(ClientRectangle.Width - m_ScrollButtonWidth * 2 - m_TabSpace - this.Margin.Right, allRect.Y, m_ScrollButtonWidth, allRect.Height);
+ DrawCell(e.Graphics, m_LeftRect, m_ScrollLeftText, m_ScrollButtonBackColorMin, m_ScrollButtonBackColorMax, m_ScrollButtonForeColor, m_ScrollButtonForeColor, false, m_FocusIndex == -2);
+ }
+
+ m_TabRect.Clear();
+ int tabWidth = allRect.Width - (showScroll ? (m_ScrollButtonWidth * 2 + m_TabSpace) : 0);
+ int tabCellWidth = (tabWidth - m_TabSpace * m_TabCount) / m_TabCount;
+ int x = allRect.X;
+ for (int i = 0; i < m_TabCount; i++)
+ {
+ if ((m_TopIndex + i) > (m_TabTextList.Count - 1)) { break; }
+ m_TabRect.Add(new Rectangle(x, allRect.Y, tabCellWidth, allRect.Height));
+
+ int dataIndex = (m_FirstColFrozen && i == 0) ? 0 : (m_TopIndex + i);
+ TabMarkFormatEventArgs eventArgs = new TabMarkFormatEventArgs(false, null, m_TabForeColor, m_SelectForeColor, m_TabKeyList[dataIndex]);
+ if (CellFormat != null)
+ {
+ CellFormat(this, eventArgs);
+ if (eventArgs.Cancel) { continue; }
+ }
+
+ DrawCell(e.Graphics, m_TabRect[i], m_TabTextList[dataIndex], m_TabBackColorMin, m_TabBackColorMax, eventArgs.TabForeColor, eventArgs.TabSelForeColor, m_SelectIndex == (m_TopIndex + i), m_FocusIndex == i);
+ x += (tabCellWidth + m_TabSpace);
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ protected override void OnMouseWheel(MouseEventArgs e)
+ {
+ if (e.Delta > 0)
+ {
+ if (m_TabKeyList.Count - m_TopIndex > m_TabCount) { m_TopIndex++; }
+ }
+ else
+ {
+ if (m_TopIndex > 0) { m_TopIndex--; }
+ }
+ base.OnMouseWheel(e);
+ Invalidate();
+ }
+
+ protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
+ {
+ if (keyData == Keys.Tab)
+ return true;
+ if (keyData == (Keys.Tab | Keys.Shift))
+ return true;
+
+ int orgIndex = m_SelectIndex;
+ switch (keyData)
+ {
+ case Keys.Left:
+ if (m_SelectIndex > 0) { m_SelectIndex--; }
+ if (m_SelectIndex == 0)
+ {
+ m_TopIndex = 0;
+ }
+ else if (m_SelectIndex == m_TopIndex)
+ {
+ m_TopIndex--;
+ }
+ if (orgIndex != m_SelectIndex)
+ {
+ Invalidate();
+ if (SelectedIndexChanged != null) { SelectedIndexChanged(this, new EventArgs()); }
+ }
+ return true;
+ case Keys.Right:
+ if (m_SelectIndex < (m_TabKeyList.Count - 1)) { m_SelectIndex++; }
+ if (m_SelectIndex == (m_TopIndex + m_TabCount - 1) && m_SelectIndex < (m_TabKeyList.Count - 1))
+ {
+ m_TopIndex++;
+ }
+ if (orgIndex != m_SelectIndex)
+ {
+ Invalidate();
+ if (SelectedIndexChanged != null) { SelectedIndexChanged(this, new EventArgs()); }
+ }
+ return true;
+ }
+
+
+ return base.ProcessCmdKey(ref msg, keyData);
+ }
+
+ protected override void OnMouseClick(MouseEventArgs e)
+ {
+ base.OnMouseClick(e);
+
+ HitTestInfo item = HitTest(e.X, e.Y);
+ if (item.Type == HitDownSquare.Tab)
+ {
+ int orgIndex = m_SelectIndex;
+
+ if (m_FirstColFrozen && item.Index == 0)
+ {
+ m_SelectIndex = 0;
+ }
+ else
+ {
+ m_SelectIndex = m_TopIndex + item.Index;
+
+ if ((item.Index + 1) == m_TabCount && m_SelectIndex < (m_TabKeyList.Count - 1))
+ {
+ //最後へ
+ m_TopIndex++;
+ }
+ else if (item.Index == 0 && m_SelectIndex != 0)
+ {
+ m_TopIndex--;
+ }
+ }
+ //m_LastMouseClickIndex = item.Index;
+
+ Invalidate();
+ if (orgIndex != m_SelectIndex)
+ {
+ if (SelectedIndexChanged != null) { SelectedIndexChanged(this, new EventArgs()); }
+ }
+ }
+ else if (item.Type == HitDownSquare.Left)
+ {
+ if (m_TopIndex > 0) { m_TopIndex--; }
+ }
+ else if (item.Type == HitDownSquare.Right)
+ {
+ if (m_TabKeyList.Count - m_TopIndex > m_TabCount) { m_TopIndex++; }
+ }
+ }
+
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ HitTestInfo item = HitTest(e.X, e.Y);
+ m_FocusIndex = -1;
+ if (item.Type == HitDownSquare.Tab)
+ {
+ m_FocusIndex = item.Index;
+ }
+ else if (item.Type == HitDownSquare.Left)
+ {
+ m_FocusIndex = -2;
+ }
+ else if (item.Type == HitDownSquare.Right)
+ {
+ m_FocusIndex = -3;
+ }
+
+ Invalidate();
+ base.OnMouseMove(e);
+ }
+
+ protected override void OnMouseLeave(System.EventArgs e)
+ {
+ //HitTestInfo item = HitTest(MousePosition.X, MousePosition.Y);
+ m_FocusIndex = -1;
+ Invalidate();
+ base.OnMouseLeave(e);
+ }
+
+ protected override void OnMouseDown(MouseEventArgs e)
+ {
+ m_IsMouseDown = true;
+ Invalidate();
+ base.OnMouseDown(e);
+ }
+
+ protected override void OnMouseUp(MouseEventArgs e)
+ {
+ m_IsMouseDown = false;
+ Invalidate();
+ base.OnMouseUp(e);
+ }
+
+ protected override void OnResize(System.EventArgs e)
+ {
+ Invalidate();
+ base.OnResize(e);
+ }
+
+ #endregion
+
+ #region ★★★★★ Private Function ★★★★★
+
+ private void DrawCell(Graphics g, Rectangle ClientRectangle, string text, Color minBackColor, Color maxBackColor, Color foreColor, Color selForeColor, bool isSel, bool isFocus)
+ {
+ bool newflag = false;
+
+ //背景
+ Color bColorMax = isFocus ? (m_IsMouseDown ? m_MouseDownBackColorMax : (isSel ? ControlPaint.Light(m_SelectBackColorMin) : m_FocusBackColorMin)) : (isSel ? m_SelectBackColorMin : minBackColor);
+ Color bColorMin = isFocus ? (m_IsMouseDown ? m_MouseDownBackColorMin : (isSel ? ControlPaint.Light(m_SelectBackColorMax) : m_FocusBackColorMax)) : (isSel ? m_SelectBackColorMax : maxBackColor);
+ using (LinearGradientBrush lgBrush = new LinearGradientBrush(ClientRectangle, bColorMax, bColorMin, LinearGradientMode.Vertical))
+ {
+ lgBrush.SetSigmaBellShape(0.4f, 0.45f); //创建基于钟形曲线的渐变过渡过程。
+
+ if (m_TabStyle == AngleStyle.Round)
+ {
+ GdiPlus.FillRoundRectangle(g, lgBrush, ClientRectangle, m_CornerRadius);
+ }
+ else
+ {
+ g.FillRectangle(lgBrush, ClientRectangle);
+ }
+ }
+
+ //枠線
+ Color bColor = (isFocus || isSel) ? m_SelectBorderColor : m_BorderColor;
+ newflag = bColor.IsSystemColor;
+ Pen pen = newflag ? SystemPens.FromSystemColor(bColor) : new Pen(bColor);
+ if (m_TabStyle == AngleStyle.Round)
+ {
+ GdiPlus.DrawRoundRectangle(g, pen, ClientRectangle, m_CornerRadius);
+ }
+ else
+ {
+ g.DrawRectangle(pen, ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
+ }
+ if (!newflag) { pen.Dispose(); }
+
+ //文言
+ newflag = foreColor.IsSystemColor;
+ Color brushColor = isFocus ? (isSel ? ControlPaint.Light(selForeColor) : ControlPaint.Light(foreColor)) : (isSel ? selForeColor : foreColor);
+ SolidBrush brush = newflag ? SystemBrushes.FromSystemColor(brushColor) as SolidBrush : new SolidBrush(brushColor);
+ if (isSel)
+ {
+ using (Font f = new Font(this.Font, FontStyle.Bold)) { g.DrawString(text, f, brush, ClientRectangle, m_StringFormat); }
+ }
+ else
+ {
+ g.DrawString(text, this.Font, brush, ClientRectangle, m_StringFormat);
+ }
+ if (!newflag) { brush.Dispose(); }
+ }
+
+
+ #endregion
+
+ #region ★★★★★ Public Function ★★★★★
+
+ public HitTestInfo HitTest(int x, int y)
+ {
+ HitTestInfo hit = new HitTestInfo();
+ hit.x = x;
+ hit.y = y;
+
+ if (m_LeftRect.Contains(x, y))
+ {
+ hit.type = HitDownSquare.Left;
+ return hit;
+ }
+
+ if (m_RightRect.Contains(x, y))
+ {
+ hit.type = HitDownSquare.Right;
+ return hit;
+ }
+
+ for (int i = 0; i < m_TabRect.Count; i++)
+ {
+ if (m_TabRect[i].Contains(x, y))
+ {
+ hit.type = HitDownSquare.Tab;
+ hit.index = i;
+ return hit;
+ }
+ }
+
+ return HitTestInfo.Nowhere;
+ }
+
+ public void AddItem(string key, string text)
+ {
+ m_TabKeyList.Add(key);
+ m_TabTextList.Add(text);
+ Invalidate();
+ }
+
+ public void Clear()
+ {
+ m_FocusIndex = -1;
+ m_SelectIndex = 0;
+ m_TopIndex = 0;
+ m_TabKeyList.Clear();
+ m_TabTextList.Clear();
+ Invalidate();
+ }
+
+ public void RemoveItem(string key)
+ {
+ int index = m_TabKeyList.IndexOf(key);
+ if (index >= 0) { RemoveItem(index); }
+ }
+
+ public void RemoveItem(int index)
+ {
+ m_TabKeyList.RemoveAt(index);
+ m_TabTextList.RemoveAt(index);
+ Invalidate();
+ }
+
+ public void SetIndexByKey(string key)
+ {
+ for (int i = 0; i < m_TabKeyList.Count; i++)
+ {
+ if (m_TabKeyList[i] == key)
+ {
+ SelectIndex = i;
+ break;
+ }
+ }
+ }
+
+ public string GetKey(int index)
+ {
+ return m_TabKeyList.Count == 0 ? string.Empty : m_TabKeyList[index];
+ }
+
+ public string GetText(int index)
+ {
+ return m_TabTextList.Count == 0 ? string.Empty : m_TabTextList[index];
+ }
+
+ #endregion
+ }
+}
--
Gitblit v1.10.0