Browse Source

Bug 1393: Close button on session tab + Non-active session can be closed, as well as disconnected sessions without a need to reconnect them

https://winscp.net/tracker/1393

Source commit: b63ce2363155fa969d91e07f294cca9f1c5c2a98
Martin Prikryl 7 years ago
parent
commit
c48fb16840

+ 5 - 2
source/DScpComp.cbproj

@@ -49,8 +49,8 @@
 		<ILINK_Description>WinSCP components</ILINK_Description>
 		<ILINK_GenerateImportLibrary>true</ILINK_GenerateImportLibrary>
 		<ILINK_GenerateLibFile>true</ILINK_GenerateLibFile>
-		<ILINK_LibraryPath>components\;$(ILINK_LibraryPath)</ILINK_LibraryPath>
-		<IncludePath>components\;core;packages\filemng;packages\dragndrop;packages\my;$(BDS)\include;$(BDS)\include\windows\vcl;$(IncludePath)</IncludePath>
+		<ILINK_LibraryPath>components\;$(INTERM_PATH)\Win32\Debug;$(ILINK_LibraryPath)</ILINK_LibraryPath>
+		<IncludePath>components\;core;packages\filemng;packages\dragndrop;packages\my;packages\tb2k;packages\tbx;$(BDS)\include;$(BDS)\include\windows\vcl;$(IncludePath)</IncludePath>
 		<IntermediateOutputDir>$(INTERM_PATH)\$(Platform)\$(Config)</IntermediateOutputDir>
 		<LinkPackageStatics>rtl.lib;vcl.lib;vclx.lib</LinkPackageStatics>
 		<Multithreaded>true</Multithreaded>
@@ -116,6 +116,9 @@
 			<BuildOrder>20</BuildOrder>
 			<BuildOrder>1</BuildOrder>
 		</PackageImport>
+		<PackageImport Include="tbxp.bpi">
+			<BuildOrder>8</BuildOrder>
+		</PackageImport>
 		<PackageImport Include="vcl.bpi">
 			<BuildOrder>23</BuildOrder>
 			<BuildOrder>0</BuildOrder>

+ 1 - 1
source/RScpComp.cbproj

@@ -38,7 +38,7 @@
 		<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;$(DCC_Namespace)</DCC_Namespace>
 		<FinalOutputDir>$(INTERM_PATH)\$(Platform)\$(Config)</FinalOutputDir>
 		<ILINK_LibraryPath>$(BDS)\lib;$(ILINK_LibraryPath)</ILINK_LibraryPath>
-		<IncludePath>components\;core;windows;packages\filemng;packages\dragndrop;packages\my;$(BDS)\include;$(BDS)\include\windows\vcl;$(IncludePath)</IncludePath>
+		<IncludePath>components\;core;windows;packages\filemng;packages\dragndrop;packages\my;packages\tb2k;packages\tbx;$(BDS)\include;$(BDS)\include\windows\vcl;$(IncludePath)</IncludePath>
 		<IntermediateOutputDir>$(INTERM_PATH)\$(Platform)\$(Config)</IntermediateOutputDir>
 		<Manifest_File>None</Manifest_File>
 		<Multithreaded>true</Multithreaded>

+ 275 - 47
source/components/ThemePageControl.cpp

@@ -5,6 +5,8 @@
 #include <Common.h>
 #include <vsstyle.h>
 #include <memory>
+#include <PasTools.hpp>
+#include <TBXOfficeXPTheme.hpp>
 #include "ThemePageControl.h"
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
@@ -39,6 +41,20 @@ __fastcall TThemeTabSheet::TThemeTabSheet(TComponent * Owner) :
   TTabSheet(Owner)
 {
   FShadowed = false;
+  FShowCloseButton = false;
+}
+//----------------------------------------------------------------------------------------------------------
+void __fastcall TThemeTabSheet::Invalidate()
+{
+  TThemePageControl * ThemePageControl = dynamic_cast<TThemePageControl *>(Parent);
+  if (DebugAlwaysTrue(ThemePageControl != NULL))
+  {
+    ThemePageControl->InvalidateTab(TabIndex);
+  }
+  else
+  {
+    Parent->Invalidate();
+  }
 }
 //----------------------------------------------------------------------------------------------------------
 void __fastcall TThemeTabSheet::SetShadowed(bool Value)
@@ -46,16 +62,16 @@ void __fastcall TThemeTabSheet::SetShadowed(bool Value)
   if (Shadowed != Value)
   {
     FShadowed = Value;
-
-    TThemePageControl * ThemePageControl = dynamic_cast<TThemePageControl *>(Parent);
-    if (DebugAlwaysTrue(ThemePageControl != NULL))
-    {
-      ThemePageControl->InvalidateTab(TabIndex);
-    }
-    else
-    {
-      Parent->Invalidate();
-    }
+    Invalidate();
+  }
+}
+//----------------------------------------------------------------------------------------------------------
+void __fastcall TThemeTabSheet::SetShowCloseButton(bool Value)
+{
+  if (ShowCloseButton != Value)
+  {
+    FShowCloseButton = Value;
+    Invalidate();
   }
 }
 //----------------------------------------------------------------------------------------------------------
@@ -64,6 +80,7 @@ __fastcall TThemePageControl::TThemePageControl(TComponent * Owner) :
   TPageControl(Owner)
 {
   FOldTabIndex = -1;
+  FHotCloseButton = -1;
 }
 //----------------------------------------------------------------------------------------------------------
 int __fastcall TThemePageControl::GetTabsHeight()
@@ -116,30 +133,46 @@ void __fastcall TThemePageControl::PaintWindow(HDC DC)
 
   // 2nd paint the inactive tabs
 
-  TPoint Point = ScreenToClient(Mouse->CursorPos);
-  int HotIndex = IndexOfTabAt(Point.X, Point.Y);
-  int SelectedIndex = TabIndex;
+  int SelectedIndex = TabIndex; // optimization
 
   for (int Tab = 0; Tab < PageCount; Tab++)
   {
     if (Tab != SelectedIndex)
     {
-      TThemeTabSheet * ThemeTabSheet = dynamic_cast<TThemeTabSheet *>(Pages[Tab]);
-      bool Shadowed = (ThemeTabSheet != NULL) ? ThemeTabSheet->Shadowed : false;
-      TRect Rect = TabRect(Tab);
-      int State = (Tab == HotIndex ? TIS_HOT : (Shadowed ? TIS_DISABLED : TIS_NORMAL));
-      DrawThemesXpTabItem(DC, Tab, Rect, false, State);
+      DrawThemesXpTab(DC, Tab);
     }
   }
 
   if (SelectedIndex >= 0)
   {
-    // 3rd paint the active selected tab
-    TRect Rect = TabRect(SelectedIndex);
-    Rect.Inflate(2, 2);
-    Rect.Bottom--;
-    DrawThemesXpTabItem(DC, SelectedIndex, Rect, false, TIS_SELECTED);
+    DrawThemesXpTab(DC, TabIndex);
+  }
+}
+//----------------------------------------------------------------------------------------------------------
+bool __fastcall TThemePageControl::HasTabCloseButton(int Index)
+{
+  TThemeTabSheet * ThemeTabSheet = dynamic_cast<TThemeTabSheet *>(Pages[Index]);
+  return (ThemeTabSheet != NULL) ? ThemeTabSheet->ShowCloseButton : false;
+}
+//----------------------------------------------------------------------------------------------------------
+void __fastcall TThemePageControl::DrawThemesXpTab(HDC DC, int Tab)
+{
+  TThemeTabSheet * ThemeTabSheet = dynamic_cast<TThemeTabSheet *>(Pages[Tab]);
+  bool Shadowed = (ThemeTabSheet != NULL) ? ThemeTabSheet->Shadowed : false;
+  TRect Rect = TabRect(Tab);
+  ItemTabRect(Tab, Rect);
+  int State;
+  if (Tab != TabIndex)
+  {
+    TPoint Point = ScreenToClient(Mouse->CursorPos);
+    int HotIndex = IndexOfTabAt(Point.X, Point.Y);
+    State = (Tab == HotIndex ? TIS_HOT : (Shadowed ? TIS_DISABLED : TIS_NORMAL));
+  }
+  else
+  {
+    State = TIS_SELECTED;
   }
+  DrawThemesXpTabItem(DC, Tab, Rect, false, State);
 }
 //----------------------------------------------------------------------------------------------------------
 // This function draws Themes Tab control parts: a) Tab-Body and b) Tab-tabs
@@ -154,6 +187,7 @@ void __fastcall TThemePageControl::DrawThemesXpTabItem(HDC DC, int Item,
   HBITMAP BitmapOld = (HBITMAP)SelectObject(DCMem, BitmapMem);
 
   TRect RectMem(0, 0, Size.Width, Size.Height);
+  TRect RectItemMem(RectMem);
   if (!Body && (State == TIS_SELECTED))
   {
     RectMem.Bottom++;
@@ -183,12 +217,7 @@ void __fastcall TThemePageControl::DrawThemesXpTabItem(HDC DC, int Item,
 
   if (!Body && (Item >= 0))
   {
-    if ((State == TIS_SELECTED))
-    {
-      RectMem.Bottom--;
-    }
-
-    DrawTabItem(DCMem, Item, RectMem, (State == TIS_SELECTED), (State == TIS_DISABLED));
+    DrawTabItem(DCMem, Item, Rect, RectItemMem, (State == TIS_SELECTED), (State == TIS_DISABLED));
   }
 
   // Blit image to the screen
@@ -198,25 +227,78 @@ void __fastcall TThemePageControl::DrawThemesXpTabItem(HDC DC, int Item,
   DeleteDC(DCMem);
 }
 //----------------------------------------------------------------------------------------------------------
-// draw tab item context: possible icon and text
-void __fastcall TThemePageControl::DrawTabItem(HDC DC, int Item, TRect Rect,
-  bool Selected, bool Shadowed)
+void __fastcall TThemePageControl::ItemTabRect(int Item, TRect & Rect)
 {
+  if (Item == TabIndex)
+  {
+    // Countered in CloseButtonRect
+    Rect.Inflate(2, 2);
+    Rect.Bottom--;
+  }
+}
+//----------------------------------------------------------------------------------------------------------
+void __fastcall TThemePageControl::ItemContentsRect(int Item, TRect & Rect)
+{
+  bool Selected = (Item == TabIndex);
+
+  Rect.Left += 6;
+  Rect.Top += 2;
+
   if (Selected)
   {
-    Rect.Bottom -= 1;
+    Rect.Bottom -= 2;
+    Rect.Top += 1;
   }
   else
   {
     Rect.Bottom += 2;
+    Rect.Top += 3;
   }
-
-  Rect.Left += 6;
-  Rect.Top += 2 + (Selected ? 1 : 3);
+}
+//----------------------------------------------------------------------------------------------------------
+bool __fastcall TThemePageControl::HasItemImage(int Item)
+{
+  return (Images != NULL) && (Pages[Item]->ImageIndex >= 0);
+}
+//----------------------------------------------------------------------------------------------------------
+void __fastcall TThemePageControl::ItemTextRect(int Item, TRect & Rect)
+{
+  if (HasItemImage(Item))
+  {
+    Rect.Left += Images->Width + 3;
+  }
+  else
+  {
+    Rect.Left -= 2;
+  }
+  Rect.Right -= 3;
+  OffsetRect(&Rect, 0, ((Item == TabIndex) ? 0 : -2));
+}
+//----------------------------------------------------------------------------------------------------------
+void __fastcall TThemePageControl::DrawCross(HDC DC, int Width, COLORREF Color, const TRect & Rect)
+{
+  HPEN Pen = CreatePen(PS_SOLID, Width, Color);
+  HPEN OldPen = static_cast<HPEN>(SelectObject(DC, Pen));
+  // To-and-back - to make both ends look the same
+  MoveToEx(DC, Rect.Left, Rect.Bottom - 1, NULL);
+  LineTo(DC, Rect.Right - 1, Rect.Top);
+  LineTo(DC, Rect.Left, Rect.Bottom - 1);
+  MoveToEx(DC, Rect.Left, Rect.Top, NULL);
+  LineTo(DC, Rect.Right - 1, Rect.Bottom - 1);
+  LineTo(DC, Rect.Left, Rect.Top);
+  SelectObject(DC, OldPen);
+  DeleteObject(Pen);
+}
+//----------------------------------------------------------------------------------------------------------
+// draw tab item context: possible icon and text
+void __fastcall TThemePageControl::DrawTabItem(
+  HDC DC, int Item, TRect TabRect, TRect Rect, bool Selected, bool Shadowed)
+{
+  ItemContentsRect(Item, Rect);
 
   UnicodeString Text = Pages[Item]->Caption;
 
-  if ((Images != NULL) && (Pages[Item]->ImageIndex >= 0))
+  if (HasItemImage(Item))
   {
     int Left;
     if (!Text.IsEmpty())
@@ -231,32 +313,132 @@ void __fastcall TThemePageControl::DrawTabItem(HDC DC, int Item, TRect Rect,
     std::unique_ptr<TCanvas> Canvas(new TCanvas());
     Canvas->Handle = DC;
     Images->Draw(Canvas.get(), Left, Y, Pages[Item]->ImageIndex, !Shadowed);
-    Rect.Left += Images->Width + 3;
-  }
-  else
-  {
-    Rect.Left -= 2;
   }
 
+  int TextHeight = 20;
   int OldMode = SetBkMode(DC, TRANSPARENT);
   if (!Text.IsEmpty())
   {
+    ItemTextRect(Item, Rect);
     HFONT OldFont = (HFONT)SelectObject(DC, Font->Handle);
-    Rect.Right -= 3;
     wchar_t * Buf = new wchar_t[Text.Length() + 1 + 4];
     wcscpy(Buf, Text.c_str());
-    TRect TextRect(0, 0, Rect.Right - Rect.Left, 20);
+    TRect TextRect(0, 0, Rect.Right - Rect.Left, TextHeight);
+    // Truncates too long texts with ellipsis
     ::DrawText(DC, Buf, -1, &TextRect, DT_CALCRECT | DT_SINGLELINE | DT_MODIFYSTRING | DT_END_ELLIPSIS);
 
-    OffsetRect(&Rect, 0, (Selected ? 0 : -2));
     DrawText(DC, Buf, -1, &Rect, DT_NOPREFIX | DT_CENTER);
     delete[] Buf;
+
+    if (HasTabCloseButton(Item))
+    {
+      Rect = CloseButtonRect(Item);
+      Rect.Offset(-TabRect.Left, -TabRect.Top);
+
+      if (FHotCloseButton == Item)
+      {
+        HBRUSH Brush = CreateSolidBrush(GetSelectedBodyColor());
+        FillRect(DC, &Rect, Brush);
+        DeleteObject(Brush);
+
+        HPEN Pen = CreatePen(PS_SOLID, 1, ColorToRGB(clHighlight));
+        HPEN OldPen = static_cast<HPEN>(SelectObject(DC, Pen));
+        Rectangle(DC, Rect.Left, Rect.Top, Rect.Right, Rect.Bottom);
+        SelectObject(DC, OldPen);
+        DeleteObject(Pen);
+      }
+
+      int CrossPadding = GetCrossPadding();
+
+      COLORREF BackColor = GetPixel(DC, Rect.Left + (Rect.Width() / 2), Rect.Top + (Rect.Height() / 2));
+      COLORREF CrossColor = ColorToRGB(Font->Color);
+      #define BlendValue(FN) (((4 * static_cast<int>(FN(BackColor))) + static_cast<int>(FN(CrossColor))) / 5)
+      COLORREF BlendColor = RGB(BlendValue(GetRValue), BlendValue(GetGValue), BlendValue(GetBValue));
+      #undef BlendValue
+
+      TRect CrossRect(Rect);
+      CrossRect.Inflate(-CrossPadding, -CrossPadding);
+
+      int CrossWidth = ScaleByTextHeight(this, 1);
+      DrawCross(DC, CrossWidth + 1, BlendColor, CrossRect);
+      DrawCross(DC, CrossWidth, CrossColor, CrossRect);
+    }
+
     SelectObject(DC, OldFont);
   }
 
   SetBkMode(DC, OldMode);
 }
 //----------------------------------------------------------------------------------------------------------
+int __fastcall TThemePageControl::CloseButtonSize()
+{
+  return ScaleByTextHeight(this, 16);
+}
+//----------------------------------------------------------------------------------------------------------
+int __fastcall TThemePageControl::GetCrossPadding()
+{
+  return ScaleByTextHeight(this, 4);
+}
+//----------------------------------------------------------------------------------------------------------
+TRect __fastcall TThemePageControl::CloseButtonRect(int Index)
+{
+  TRect Rect = TabRect(Index);
+  ItemTabRect(Index, Rect);
+  ItemContentsRect(Index, Rect);
+  ItemTextRect(Index, Rect);
+
+  int ACloseButtonSize = CloseButtonSize();
+  int CrossPadding = GetCrossPadding();
+
+  TEXTMETRIC TextMetric;
+  Canvas->Font = Font;
+  GetTextMetrics(Canvas->Handle, &TextMetric);
+
+  Rect.Top += TextMetric.tmAscent - ACloseButtonSize + CrossPadding;
+  Rect.Left = Rect.Right - ACloseButtonSize - ScaleByTextHeight(this, 1);
+  if (Index == TabIndex)
+  {
+    // To counter Inflate(2, 2) in ItemTabRect
+    Rect.Left -= 2;
+  }
+  Rect.Right = Rect.Left + ACloseButtonSize;
+  Rect.Bottom = Rect.Top + ACloseButtonSize;
+  return Rect;
+}
+//----------------------------------------------------------------------------------------------------------
+void __fastcall TThemePageControl::SetHotCloseButton(int Index)
+{
+  if (Index != FHotCloseButton)
+  {
+    if (FHotCloseButton >= 0)
+    {
+      InvalidateTab(FHotCloseButton);
+    }
+    FHotCloseButton = Index;
+    if (FHotCloseButton >= 0)
+    {
+      InvalidateTab(FHotCloseButton);
+    }
+  }
+}
+//----------------------------------------------------------------------------------------------------------
+void __fastcall TThemePageControl::MouseMove(TShiftState /*Shift*/, int X, int Y)
+{
+  SetHotCloseButton(IndexOfCloseButtonAt(X, Y));
+}
+//----------------------------------------------------------------------------------------------------------
+int __fastcall TThemePageControl::IndexOfCloseButtonAt(int X, int Y)
+{
+  int Result = IndexOfTabAt(X, Y);
+  if ((Result < 0) ||
+      !HasTabCloseButton(Result) ||
+      !CloseButtonRect(Result).Contains(TPoint(X, Y)))
+  {
+    Result = -1;
+  }
+  return Result;
+}
+//----------------------------------------------------------------------------------------------------------
 void __fastcall TThemePageControl::DrawThemesPart(HDC DC, int PartId,
   int StateId, LPCWSTR PartNameID, LPRECT Rect)
 {
@@ -267,8 +449,7 @@ void __fastcall TThemePageControl::DrawThemesPart(HDC DC, int PartId,
     CloseThemeData(Theme);
   }
 }
-//==========================================================================================================
-// these two messages are necessary only to properly redraw deselected tab background, because
+//----------------------------------------------------------------------------------------------------------
 bool __fastcall TThemePageControl::CanChange()
 {
   FOldTabIndex = ActivePageIndex;
@@ -303,6 +484,53 @@ void __fastcall TThemePageControl::Change()
   TPageControl::Change();
 }
 //----------------------------------------------------------------------------------------------------------
+UnicodeString __fastcall TThemePageControl::FormatCaptionWithCloseButton(const UnicodeString & Caption)
+{
+  int OrigWidth = Canvas->TextWidth(Caption);
+  UnicodeString Result = Caption;
+  int CloseButtonWidth = CloseButtonSize();
+  while (Canvas->TextWidth(Result) < OrigWidth + CloseButtonWidth)
+  {
+    Result += L" ";
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void __fastcall TThemePageControl::WMLButtonDown(TWMLButtonDown & Message)
+{
+  int Index = IndexOfCloseButtonAt(Message.XPos, Message.YPos);
+  if (Index >= 0)
+  {
+    Message.Result = 1;
+    if (FOnCloseButtonClick != NULL)
+    {
+      FOnCloseButtonClick(this, Index);
+    }
+  }
+  else
+  {
+    TPageControl::Dispatch(&Message);
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TThemePageControl::Dispatch(void * Message)
+{
+  TMessage * M = reinterpret_cast<TMessage*>(Message);
+  if (M->Msg == CM_MOUSELEAVE)
+  {
+    SetHotCloseButton(-1);
+    TPageControl::Dispatch(Message);
+  }
+  else if (M->Msg == WM_LBUTTONDOWN)
+  {
+    WMLButtonDown(*reinterpret_cast<TWMLButtonDown *>(M));
+  }
+  else
+  {
+    TPageControl::Dispatch(Message);
+  }
+}
+//----------------------------------------------------------------------------------------------------------
 #ifdef _DEBUG
 void __fastcall TThemePageControl::RequestAlign()
 {

+ 29 - 1
source/components/ThemePageControl.h

@@ -10,36 +10,64 @@ public:
   __fastcall TThemeTabSheet(TComponent * Owner);
 
   __property bool Shadowed = { read = FShadowed, write = SetShadowed };
+  __property bool ShowCloseButton = { read = FShowCloseButton, write = SetShowCloseButton };
 
 private:
   void __fastcall SetShadowed(bool Value);
+  void __fastcall SetShowCloseButton(bool Value);
+  void __fastcall Invalidate();
 
   bool FShadowed;
+  bool FShowCloseButton;
 };
 //---------------------------------------------------------------------------
+typedef void __fastcall (__closure *TPageControlCloseButtonClick)(TPageControl * Sender, int Index);
+//---------------------------------------------------------------------------
 class TThemePageControl : public TPageControl
 {
 friend class TThemeTabSheet;
+
+__published:
+  __property TPageControlCloseButtonClick OnCloseButtonClick = { read = FOnCloseButtonClick, write = FOnCloseButtonClick };
+
 public:
   __fastcall TThemePageControl(TComponent * Owner);
 
   int __fastcall GetTabsHeight();
+  UnicodeString __fastcall FormatCaptionWithCloseButton(const UnicodeString & Caption);
 
 protected:
   virtual void __fastcall PaintWindow(HDC DC);
   DYNAMIC bool __fastcall CanChange();
   DYNAMIC void __fastcall Change();
+  DYNAMIC void __fastcall MouseMove(TShiftState Shift, int X, int Y);
+  virtual void __fastcall Dispatch(void * Message);
   #ifdef _DEBUG
   virtual void __fastcall RequestAlign();
   #endif
 
 private:
+  void __fastcall DrawThemesXpTab(HDC DC, int Tab);
   void __fastcall DrawThemesXpTabItem(HDC DC, int Item, const TRect & Rect, bool Body, int State);
-  void __fastcall DrawTabItem(HDC DC, int Item, TRect Rect, bool Selected, bool Shadowed);
+  void __fastcall DrawTabItem(HDC DC, int Item, TRect TabRect, TRect Rect, bool Selected, bool Shadowed);
   void __fastcall DrawThemesPart(HDC DC, int PartId, int StateId, LPCWSTR PartNameID, LPRECT Rect);
   void __fastcall InvalidateTab(int Index);
+  int __fastcall CloseButtonSize();
+  int __fastcall GetCrossPadding();
+  TRect __fastcall CloseButtonRect(int Index);
+  int __fastcall IndexOfCloseButtonAt(int X, int Y);
+  void __fastcall ItemContentsRect(int Item, TRect & Rect);
+  bool __fastcall HasItemImage(int Item);
+  void __fastcall ItemTextRect(int Item, TRect & Rect);
+  void __fastcall ItemTabRect(int Item, TRect & Rect);
+  bool __fastcall HasTabCloseButton(int Index);
+  void __fastcall SetHotCloseButton(int Index);
+  void __fastcall DrawCross(HDC DC, int Width, COLORREF Color, const TRect & Rect);
+  void __fastcall WMLButtonDown(TWMLButtonDown & Message);
 
   int FOldTabIndex;
+  int FHotCloseButton;
+  TPageControlCloseButtonClick FOnCloseButtonClick;
 };
 //---------------------------------------------------------------------------
 #endif

+ 41 - 7
source/forms/CustomScpExplorer.cpp

@@ -4672,10 +4672,10 @@ void __fastcall TCustomScpExplorerForm::DuplicateSession()
   }
 }
 //---------------------------------------------------------------------------
-bool __fastcall TCustomScpExplorerForm::CanCloseQueue()
+bool __fastcall TCustomScpExplorerForm::CanCloseQueue(TTerminalQueue * Queue)
 {
-  DebugAssert(FQueue != NULL);
-  bool Result = FQueue->IsEmpty;
+  DebugAssert(Queue != NULL);
+  bool Result = Queue->IsEmpty;
   if (!Result)
   {
     SetFocus();
@@ -4684,6 +4684,11 @@ bool __fastcall TCustomScpExplorerForm::CanCloseQueue()
   return Result;
 }
 //---------------------------------------------------------------------------
+bool __fastcall TCustomScpExplorerForm::CanCloseQueue()
+{
+  return CanCloseQueue(FQueue);
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::CloseSession()
 {
   if (CanCloseQueue())
@@ -6256,7 +6261,7 @@ void __fastcall TCustomScpExplorerForm::DoTerminalListChanged()
 
     for (int Index = 0; Index <= TerminalList->Count; Index++)
     {
-      TTabSheet * TabSheet;
+      TThemeTabSheet * TabSheet;
       if (Index >= SessionsPageControl->PageCount)
       {
         TabSheet = new TThemeTabSheet(SessionsPageControl);
@@ -6264,10 +6269,11 @@ void __fastcall TCustomScpExplorerForm::DoTerminalListChanged()
       }
       else
       {
-        TabSheet = SessionsPageControl->Pages[Index];
+        TabSheet = DebugNotNull(dynamic_cast<TThemeTabSheet *>(SessionsPageControl->Pages[Index]));
       }
 
-      if (Index < TerminalList->Count)
+      bool IsSessionTab = (Index < TerminalList->Count);
+      if (IsSessionTab)
       {
         TTerminal * Terminal = dynamic_cast<TTerminal *>(TerminalList->Objects[Index]);
         TabSheet->Tag = reinterpret_cast<int>(Terminal);
@@ -6281,6 +6287,8 @@ void __fastcall TCustomScpExplorerForm::DoTerminalListChanged()
         // We know that we are at the last page, sotherwise we could not call this (it assumes that new session tab is the last one)
         UpdateNewSessionTab();
       }
+
+      TabSheet->ShowCloseButton = IsSessionTab;
     }
   }
   __finally
@@ -6325,7 +6333,7 @@ void __fastcall TCustomScpExplorerForm::UpdateSessionTab(TTabSheet * TabSheet)
       TabSheet->ImageIndex = AddSessionColor(Color);
 
       UnicodeString TabCaption = TTerminalManager::Instance()->GetTerminalTitle(ManagedTerminal, true);
-      TabSheet->Caption = TabCaption;
+      TabSheet->Caption = SessionsPageControl->FormatCaptionWithCloseButton(TabCaption);
 
       TThemeTabSheet * ThemeTabSheet = dynamic_cast<TThemeTabSheet *>(TabSheet);
       if (DebugAlwaysTrue(ThemeTabSheet != NULL))
@@ -9843,3 +9851,29 @@ void __fastcall TCustomScpExplorerForm::ReloadDirectory(TOperationSide Side)
   DirView(Side)->ReloadDirectory();
 }
 //---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::SessionsPageControlCloseButtonClick(TPageControl * /*Sender*/, int Index)
+{
+  if (Index == SessionsPageControl->TabIndex)
+  {
+    CloseSession();
+  }
+  else
+  {
+    TTabSheet * TabSheet = SessionsPageControl->Pages[Index];
+    TTerminal * Terminal = GetSessionTabTerminal(TabSheet);
+    TTerminalManager * Manager = TTerminalManager::Instance();
+    if (!Terminal->Active)
+    {
+      Manager->FreeTerminal(Terminal);
+    }
+    else
+    {
+      TTerminalQueue * Queue = Manager->FindQueueForTerminal(Terminal);
+      if (CanCloseQueue(Queue))
+      {
+        Manager->FreeTerminal(Terminal);
+      }
+    }
+  }
+}
+//---------------------------------------------------------------------------

+ 1 - 0
source/forms/CustomScpExplorer.dfm

@@ -313,6 +313,7 @@ object CustomScpExplorerForm: TCustomScpExplorerForm
     OnDragDrop = SessionsPageControlDragDrop
     OnDragOver = SessionsPageControlDragOver
     OnMouseDown = SessionsPageControlMouseDown
+    OnCloseButtonClick = SessionsPageControlCloseButtonClick
     object TabSheet1: TTabSheet
       Caption = 'TabSheet1'
     end

+ 2 - 0
source/forms/CustomScpExplorer.h

@@ -189,6 +189,7 @@ __published:
   void __fastcall DirViewBusy(TObject *Sender, int Busy, bool & Allow);
   void __fastcall SessionsPageControlContextPopup(TObject *Sender, TPoint &MousePos, bool &Handled);
   void __fastcall DockContextPopup(TObject *Sender, TPoint &MousePos, bool &Handled);
+  void __fastcall SessionsPageControlCloseButtonClick(TPageControl *Sender, int Index);
 
 private:
   TTerminal * FTerminal;
@@ -450,6 +451,7 @@ protected:
   void __fastcall QueueViewDeleteItem(int Index);
   void __fastcall UserActionTimer(TObject * Sender);
   void __fastcall UpdateQueueView();
+  bool __fastcall CanCloseQueue(TTerminalQueue * Queue);
   bool __fastcall CanCloseQueue();
   virtual bool __fastcall IsFileControl(TObject * Control, TOperationSide Side);
   virtual void __fastcall ReloadLocalDirectory(const UnicodeString Directory = L"");

+ 8 - 1
source/packages/tbx/TBXOfficeXPTheme.pas

@@ -86,6 +86,8 @@ type
     procedure PaintStatusBar(Control: TWinControl; Canvas: TCanvas; R: TRect; Part: Integer); override;
   end;
 
+function GetSelectedBodyColor: TColor;
+
 implementation
 
 uses
@@ -1227,7 +1229,7 @@ begin
     BtnItemColors[bisDisabled, ipText]         := DisabledText;
     SetContrast(BtnItemColors[bisDisabled, ipText], ToolbarColor, 80);
     BtnItemColors[bisDisabled, ipFrame]        := clNone;
-    BtnItemColors[bisSelected, ipBody]         := Blend(clHighlight, Blend(clBtnFace, clWindow, 50), 10);
+    BtnItemColors[bisSelected, ipBody]         := GetSelectedBodyColor;
     SetContrast(BtnItemColors[bisSelected, ipBody], ToolbarColor, 5);
     BtnItemColors[bisSelected, ipText]         := BtnItemColors[bisNormal, ipText];
     BtnItemColors[bisSelected, ipFrame]        := clHighlight;
@@ -1409,4 +1411,9 @@ begin
   if Message.WParam = TSC_VIEWCHANGE then SetupColorCache;
 end;
 
+function GetSelectedBodyColor: TColor;
+begin
+  Result := Blend(clHighlight, Blend(clBtnFace, clWindow, 50), 10);
+end;
+
 end.