//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1998 by Yura Bidus, All Rights Reserved
//
/// \file
/// Implementation of the private TTraceWindow class.
//----------------------------------------------------------------------------
#include <owl/pch.h>
#pragma hdrstop
#include "tracewnd.h"
#include <owl/opensave.h>
#include <owl/inputdia.h>
#include <owl/panespli.h>
#include <owl/profile.h>
#include <owl/edit.h>
#include <owl/listbox.h>
#include <owl/checklst.h>
#include <owl/validate.h>
using namespace std;
using namespace std::filesystem;
namespace owl {
OWL_DIAGINFO;
auto GetDiagIniFullFileName_(LPCSTR filename) -> tstring; // Private function defined in "diaginit.cpp".
//-------------------------------------------------------------------------------------------------
class TTraceWindow::TGroupList
: public TCheckList
{
public:
TGroupList(TWindow* parent, int id)
: TCheckList(parent, id, 0, 0, 0, 0)
{}
void AddItem(const tstring& text, bool checked, TDiagBase* group)
{
const auto i = new TCheckListItem(text, checked ? BF_CHECKED : BF_UNCHECKED);
i->SetData(reinterpret_cast<UINT_PTR>(group));
TCheckList::AddItem(i);
}
auto GetData(int index) -> TDiagBase*
{
PRECONDITION(GetItem(index));
return reinterpret_cast<TDiagBase*>(GetItem(index)->GetData());
}
void SetItemText(int index, const tstring& text)
{
PRECONDITION(GetItem(index));
GetItem(index)->SetText(text);
}
private:
void EvLButtonDown(uint modKeys, const TPoint& point)
{
TCheckList::EvLButtonDown(modKeys, point);
const auto index = GetCaretIndex();
const auto i = GetItem(index);
if (i && i->IsEnabled() && point.x < CheckList_BoxWidth)
SetGroupState(index, i->IsChecked());
}
void EvChar(uint key, uint repeatCount, uint flags)
{
TCheckList::EvChar(key, repeatCount, flags);
const auto index = GetCaretIndex();
const auto i = GetItem(index);
if (i && i->IsEnabled() && key == _T(' '))
SetGroupState(index, i->IsChecked());
}
void SetGroupState(int index, bool enabled)
{
const auto group = GetData(index);
if (!group) return;
group->Enable(enabled);
// Save level into "OWL.INI".
//
auto s = tostringstream{};
s << (group->IsEnabled() ? 1 : 0) << _T(' ') << group->GetLevel();
TProfile ini(_T("Diagnostics"), GetDiagIniFullFileName_(OWL_INI));
ini.WriteString(to_tstring(group->GetName()), s.str());
}
DECLARE_RESPONSE_TABLE(TGroupList);
};
DEFINE_RESPONSE_TABLE1(TTraceWindow::TGroupList, TCheckList)
EV_WM_LBUTTONDOWN,
EV_WM_CHAR,
END_RESPONSE_TABLE;
//-------------------------------------------------------------------------------------------------
namespace {
enum
{
Cm_SaveTrace = 100,
Cm_Top,
Cm_EditClear,
Cm_EditSelectAll,
Cm_SetLevel,
Cm_NextPane,
Cm_PreviousPane
};
enum
{
Idc_TraceEdit = 200,
Idc_ModuleListBox,
Idc_GroupListBox
};
} // namespace
TTraceWindow* TTraceWindow::Instance = nullptr;
DEFINE_RESPONSE_TABLE1(TTraceWindow, TFrameWindow)
EV_COMMAND(Cm_SaveTrace, CmSave),
EV_COMMAND(Cm_Top, CmTop),
EV_COMMAND_ENABLE(Cm_Top, CeTop),
EV_COMMAND(Cm_EditClear, CmEditClear),
EV_COMMAND(Cm_EditSelectAll, CmEditSelectAll),
EV_COMMAND(Cm_SetLevel, CmSetLevel),
EV_COMMAND_ENABLE(Cm_SetLevel, CeSetLevel),
EV_COMMAND(Cm_NextPane, CmNextPane),
EV_COMMAND(Cm_PreviousPane, CmPreviousPane),
EV_LBN_SELCHANGE(Idc_ModuleListBox, LbnSelChangeModule),
EV_LBN_DBLCLK(Idc_GroupListBox, CmSetLevel),
EV_WM_ACTIVATE,
END_RESPONSE_TABLE;
auto TTraceWindow::GetInstance(bool shouldCreateIfNeccessary) -> TTraceWindow*
{
if (shouldCreateIfNeccessary && !Instance)
Instance = new TTraceWindow{};
return Instance;
}
void TTraceWindow::DestroyInstance()
{
if (Instance)
delete Instance;
}
void TTraceWindow::Output(TDiagBase* group, LPCTSTR msg)
{
PRECONDITION(group);
// Don't process trace messages from the Diagnostic Window itself.
//
if (Active) return;
TraceText += group->GetDiagModule()->GetName();
TraceText += _T(": ");
TraceText += msg;
TraceDirty = true;
}
auto TTraceWindow::IdleAction(long idleCount) -> bool
{
const auto more = TFrameWindow::IdleAction(idleCount);
if (TraceDirty)
UpdateTraceText();
return more;
}
void TTraceWindow::SetupWindow()
{
TFrameWindow::SetupWindow();
const auto title = tstring{_T("Diagnostic Window - ")} +
GetApplication()->GetMainWindow()->GetCaption();
SetWindowText(title);
//
// Build the menu in code to not force a linked in RC file:
//
TMenu menu{NoAutoDelete};
TPopupMenu traceMenu{NoAutoDelete};
menu.AppendMenu(MF_POPUP, traceMenu, _T("&Trace"));
traceMenu.AppendMenu(MF_STRING, Cm_SaveTrace, _T("&Save messages...\tCtrl+S"));
traceMenu.AppendMenu(MF_SEPARATOR);
traceMenu.AppendMenu(MF_STRING, Cm_Top, _T("Always On &Top"));
TPopupMenu editMenu{NoAutoDelete};
menu.AppendMenu(MF_POPUP, editMenu, _T("&Edit"));
editMenu.AppendMenu(MF_STRING, CM_EDITCOPY, _T("&Copy\tCtrl+C"));
editMenu.AppendMenu(MF_STRING, Cm_EditClear, _T("C&lear"));
editMenu.AppendMenu(MF_SEPARATOR);
editMenu.AppendMenu(MF_STRING, Cm_EditSelectAll, _T("Select &all\tCtrl+A"));
TPopupMenu traceGroupMenu{NoAutoDelete};
menu.AppendMenu(MF_POPUP, traceGroupMenu, _T("&Group"));
traceGroupMenu.AppendMenu(MF_STRING, Cm_SetLevel, _T("Set level...\tEnter"));
SetMenu(menu);
DrawMenuBar();
const ACCEL accelerators[6]
{
{FVIRTKEY, VK_F11, Cm_SaveTrace},
{FVIRTKEY | FCONTROL, 'S', Cm_SaveTrace},
{FVIRTKEY | FCONTROL, 'A', Cm_EditSelectAll},
{FVIRTKEY, VK_RETURN, Cm_SetLevel},
{FVIRTKEY, VK_F6, Cm_NextPane},
{FVIRTKEY | FSHIFT, VK_F6, Cm_PreviousPane}
};
SethAccel(CreateAcceleratorTable(const_cast<LPACCEL>(accelerators), static_cast<int>(COUNTOF(accelerators))));
TraceMessages.LimitText(0);
Panels.SplitPane(&TraceMessages, nullptr, psNone);
Panels.SplitPane(&TraceMessages, &TraceModules, psHorizontal, 0.50f);
Panels.SplitPane(&TraceModules, &TraceGroups, psVertical, 0.50f);
const auto font = TDefaultGuiFont{TDefaultGuiFont::sfiMessage};
TraceMessages.SetWindowFont(font, false);
TraceModules.SetWindowFont(font, false);
TraceGroups.SetWindowFont(font, false);
AddModules(TModule::NextModule(0));
OldHook = TDiagBase::SetGlobalHook(this);
}
void TTraceWindow::CleanupWindow()
{
TDiagBase::SetGlobalHook(OldHook);
SaveWindowState();
TFrameWindow::CleanupWindow();
}
TTraceWindow::TTraceWindow()
: TFrameWindow(nullptr, _T("Diagnostic Window")),
OldHook{},
Panels{*new TPaneSplitter{this}},
TraceMessages{*new TEdit{this, Idc_TraceEdit, _T(""), 0, 0, 0, 0, 0, true}},
TraceGroups{*new TGroupList(this, Idc_GroupListBox)},
TraceModules{*new TListBox(this, Idc_ModuleListBox, 0, 0, 0, 0)},
TraceDirty{false},
Active{true},
TracePath{to_tstring(current_path())},
TraceText{}
{
Instance = this;
SetClientWindow(&Panels);
TraceMessages.ModifyStyle(WS_POPUP | WS_BORDER, ES_READONLY | WS_CHILD);
TraceModules.ModifyStyle(WS_POPUP | WS_BORDER | LBS_SORT, WS_CHILD | LBS_NOINTEGRALHEIGHT);
TraceModules.ModifyExStyle(0, WS_EX_CLIENTEDGE);
TraceGroups.ModifyStyle(WS_POPUP | WS_BORDER, WS_CHILD | LBS_SORT);
RestoreWindowState();
}
TTraceWindow::~TTraceWindow()
{
Destroy();
Instance = nullptr;
}
void TTraceWindow::CmSave()
{
auto data = TFileSaveDialog::TData{};
data.SetFilter(_T("Text Files (*.txt)|*.txt|All Files (*.*)|*.*|"));
data.Flags |= OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT;
data.InitialDir = const_cast<tchar*>(TracePath.c_str());
auto f = to_tstring(path{TracePath} / _T("Trace-000.txt"));
const auto n = f.size();
auto i = size_t{0};
while (exists(f))
{
++i;
if (i > 999)
break;
const auto makeDigit = [](size_t i) { return static_cast<tchar>('0' + (i % 10)); };
f[n - 5] = makeDigit(i / 1);
f[n - 6] = makeDigit(i / 10);
f[n - 7] = makeDigit(i / 100);
}
_tcscpy_s(data.FileName, data.MaxPath, to_tstring(path{f}.filename()).c_str());
TFileSaveDialog dlg{this, data};
if (dlg.Execute() == IDOK)
{
const auto& s = TraceMessages.GetText();
tofstream{data.FileName} << s;
TracePath = to_tstring(path{data.FileName}.parent_path());
}
}
void TTraceWindow::CmTop()
{
Attr.ExStyle ^= WS_EX_TOPMOST; // toggle the bit
const auto topMost = (Attr.ExStyle & WS_EX_TOPMOST) != 0;
SetWindowPos(topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
auto ini = TProfile{_T("TraceWindow"), GetDiagIniFullFileName_(OWL_INI)};
ini.WriteInt(_T("TopMost"), topMost ? 1 : 0);
}
void TTraceWindow::CeTop(TCommandEnabler& tce)
{
tce.SetCheck((Attr.ExStyle & WS_EX_TOPMOST) != 0);
}
void TTraceWindow::CmEditClear()
{
TraceMessages.SetWindowText(_T(""));
}
void TTraceWindow::CmEditSelectAll()
{
TraceMessages.SetSelection(0, -1);
TraceMessages.SetFocus();
}
void TTraceWindow::CmSetLevel()
{
const auto i = TraceGroups.GetSelIndex();
if (i == LB_ERR) return;
const auto group = reinterpret_cast<TDiagBase*>(TraceGroups.GetData(i));
auto s = tostringstream{};
s << group->GetLevel();
const auto levelString = s.str();
auto prompt = tstring{_T("Set level for ")};
prompt += FormatLink(group->GetDiagModule());
prompt += _T("::");
prompt += to_tstring(group->GetName());
const auto m = TModule::FindResModule(IDD_INPUTDIALOG, TResId{RT_DIALOG});
TInputDialog dlg{&TraceGroups, _T("Diagnostic Window"), prompt, levelString, m, new TRangeValidator{INT_MIN, INT_MAX}};
if (dlg.Execute())
{
auto level = 0;
tistringstream{dlg.GetBuffer()} >> level;
group->SetLevel(level);
TraceGroups.SetItemText(i, FormatGroup(group).c_str());
// Save the state to the configuration file.
//
auto ini = TProfile{_T("Diagnostics"), GetDiagIniFullFileName_(OWL_INI)};
auto s = tostringstream{};
s << (group->IsEnabled() ? 1 : 0) << _T(' ') << level;
ini.WriteString(to_tstring(group->GetName()), s.str());
}
}
void TTraceWindow::CeSetLevel(TCommandEnabler& tce)
{
tce.Enable(TraceGroups.GetSelIndex() != LB_ERR);
}
void TTraceWindow::CmNextPane()
{
const auto w = GetFocus();
if (w == TraceMessages.GetHandle())
TraceModules.SetFocus();
else if (w == TraceModules.GetHandle())
TraceGroups.SetFocus();
else
TraceMessages.SetFocus();
}
void TTraceWindow::CmPreviousPane()
{
const auto w = GetFocus();
if (w == TraceMessages.GetHandle())
TraceGroups.SetFocus();
else if (w == TraceModules.GetHandle())
TraceMessages.SetFocus();
else
TraceModules.SetFocus();
}
void TTraceWindow::EvActivate(uint active, bool minimized, HWND hWndOther)
{
TWindow::EvActivate(active, minimized, hWndOther);
Active = active;
}
void TTraceWindow::LbnSelChangeModule()
{
const auto i = TraceModules.GetSelIndex();
if (i == LB_ERR) return;
const auto module = reinterpret_cast<TModule*>(TraceModules.GetItemData(i));
TraceGroups.SetRedraw(false);
TraceGroups.ClearList();
for (auto group = TDiagBase::GetDiagGroup(); group; group = TDiagBase::GetDiagGroup(group))
if (group->GetDiagModule() == module)
TraceGroups.AddItem(FormatGroup(group), group->IsEnabled(), group);
TraceGroups.SetRedraw(true);
TraceGroups.Invalidate();
}
void TTraceWindow::SaveWindowState()
{
auto ini = TProfile{_T("TraceWindow"), GetDiagIniFullFileName_(OWL_INI)};
auto s = tostringstream{};
const auto ws = _T(' ');
s << Attr.X << ws << Attr.Y << ws << (Attr.X + Attr.W) << ws << (Attr.Y + Attr.H);
ini.WriteString(_T("Position"), s.str());
ini.WriteString(_T("Path"), TracePath.c_str());
}
void TTraceWindow::RestoreWindowState()
{
auto ini = TProfile{_T("TraceWindow"), GetDiagIniFullFileName_(OWL_INI)};
auto is = tistringstream{ini.GetString(_T("Position"), _T("-1 -1 -1 -1"))};
auto r = TRect{};
is >> r.left >> r.top >> r.right >> r.bottom;
if (!r.IsEmpty())
{
Attr.X = r.left;
Attr.Y = r.top;
Attr.W = r.Width();
Attr.H = r.Height();
}
bool topMost = ini.GetInt(_T("TopMost"), 0) == 1;
if (topMost)
ModifyExStyle(0, WS_EX_TOPMOST);
TracePath = ini.GetString(_T("Path"), TracePath);
}
void TTraceWindow::AddModules(TModule* module)
{
for (auto m = module; m; m = TModule::NextModule(m))
{
const auto i = TraceModules.AddString(FormatLink(m));
TraceModules.SetItemData(i, reinterpret_cast<LPARAM>(m));
}
for (auto g = TDiagBase::GetDiagGroup(0); g; g = TDiagBase::GetDiagGroup(g))
if (g->GetDiagModule() == module)
TraceGroups.AddItem(FormatGroup(g), g->IsEnabled(), g);
}
void TTraceWindow::UpdateTraceText()
{
if (!IsWindow()) return;
const auto tmp = TraceText;
TraceText = _T("");
TraceDirty = false;
const auto n = TraceMessages.GetWindowTextLength();
TraceMessages.SetSelection(n, -1);
TraceMessages.Insert(tmp);
}
auto TTraceWindow::FormatGroup(TDiagBase* group) -> tstring
{
const auto n = to_tstring(group->GetName());
auto ini = TProfile{_T("Diagnostics Descriptions"), GetDiagIniFullFileName_(OWL_INI)};
const auto d = ini.GetString(n, n); // Replace name by description, if present.
auto s = tostringstream{};
s << d << _T(" (") << group->GetLevel() << _T(')');
return s.str();
}
auto TTraceWindow::FormatLink(TModule* module) -> tstring
{
return module->GetName();
}
} // OWL namespace
↑ V1004 The 'group' pointer was used unsafely after it was verified against nullptr. Check lines: 158, 164.
↑ V811 Decreased performance. Excessive type casting: string -> char * -> string. Consider inspecting second argument of the function SetItemText.
↑ V815 Decreased performance. Consider replacing the expression 'TraceText = ""' with 'TraceText.clear()'.