403 wxPanel *favPanel =
new wxPanel(
m_panel, wxID_ANY);
404 favPanel->SetBackgroundColour(wxColour(35, 35, 35));
406 wxBoxSizer *favSizer =
new wxBoxSizer(wxHORIZONTAL);
409 m_treeSearchCtrl =
new wxSearchCtrl(favPanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, -1));
419 wxStaticLine *separator =
new wxStaticLine(favPanel, wxID_ANY, wxDefaultPosition, wxSize(2, 25), wxLI_VERTICAL);
420 separator->SetBackgroundColour(wxColour(100, 100, 100));
421 favSizer->Add(separator, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
424 wxStaticText *favLabel =
new wxStaticText(favPanel, wxID_ANY,
"Favorites:");
425 favLabel->SetForegroundColour(wxColour(200, 200, 200));
426 favSizer->Add(favLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
429 m_xmlFilterBtn =
new wxToggleButton(favPanel, wxID_ANY,
"All XML");
435 m_pngFilterBtn =
new wxToggleButton(favPanel, wxID_ANY,
"All PNG");
447 favSizer->AddStretchSpacer(1);
450 wxStaticLine *viewSeparator =
new wxStaticLine(favPanel, wxID_ANY, wxDefaultPosition, wxSize(2, 25), wxLI_VERTICAL);
451 viewSeparator->SetBackgroundColour(wxColour(100, 100, 100));
452 favSizer->Add(viewSeparator, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
455 wxStaticText *viewLabel =
new wxStaticText(favPanel, wxID_ANY,
"View:");
456 viewLabel->SetForegroundColour(wxColour(200, 200, 200));
457 favSizer->Add(viewLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
474 favPanel->SetSizer(favSizer);
475 parentSizer->Add(favPanel, 0, wxEXPAND | wxALL, 2);
562 wxBoxSizer *containerSizer =
new wxBoxSizer(wxHORIZONTAL);
567 wxBoxSizer *navSizer =
new wxBoxSizer(wxHORIZONTAL);
570 wxString leftArrowPath =
m_rootPath + wxFileName::GetPathSeparator() +
"buttons" + wxFileName::GetPathSeparator() +
571 "left_arrow" + wxFileName::GetPathSeparator();
572 wxBitmap gridBackNormal(leftArrowPath +
"LeftArrow_Normal_20.png", wxBITMAP_TYPE_PNG);
573 wxBitmap gridBackHovered(leftArrowPath +
"LeftArrow_Hovered_20.png", wxBITMAP_TYPE_PNG);
574 wxBitmap gridBackPressed(leftArrowPath +
"LeftArrow_Pressed_20.png", wxBITMAP_TYPE_PNG);
575 wxBitmap gridBackDisabled(leftArrowPath +
"LeftArrow_Disabled_20.png", wxBITMAP_TYPE_PNG);
578 new wxBitmapButton(
m_navigationPanel, wxID_ANY, gridBackNormal, wxDefaultPosition, wxSize(36, 36));
589 wxString rightArrowPath =
m_rootPath + wxFileName::GetPathSeparator() +
"buttons" + wxFileName::GetPathSeparator() +
590 "right_arrow" + wxFileName::GetPathSeparator();
591 wxBitmap gridForwardNormal(rightArrowPath +
"RightArrow_Normal_20.png", wxBITMAP_TYPE_PNG);
592 wxBitmap gridForwardHovered(rightArrowPath +
"RightArrow_Hovered_20.png", wxBITMAP_TYPE_PNG);
593 wxBitmap gridForwardPressed(rightArrowPath +
"RightArrow_Pressed_20.png", wxBITMAP_TYPE_PNG);
594 wxBitmap gridForwardDisabled(rightArrowPath +
"RightArrow_Disabled_20.png", wxBITMAP_TYPE_PNG);
597 new wxBitmapButton(
m_navigationPanel, wxID_ANY, gridForwardNormal, wxDefaultPosition, wxSize(36, 36));
618 containerSizer->Add(
m_navigationPanel, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxTOP | wxBOTTOM, 5);
633 wxBoxSizer *functionalSizer =
new wxBoxSizer(wxHORIZONTAL);
636 wxString historyPath =
m_rootPath + wxFileName::GetPathSeparator() +
"buttons" + wxFileName::GetPathSeparator() +
637 "history" + wxFileName::GetPathSeparator();
638 wxBitmap historyNormal(historyPath +
"History_Normal_20.png", wxBITMAP_TYPE_PNG);
639 wxBitmap historyHovered(historyPath +
"History_Hovered_20.png", wxBITMAP_TYPE_PNG);
640 wxBitmap historyPressed(historyPath +
"History_Pressed_20.png", wxBITMAP_TYPE_PNG);
641 wxBitmap historyDisabled(historyPath +
"History_Disabled_20.png", wxBITMAP_TYPE_PNG);
650 functionalSizer->Add(
m_historyButton, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 10);
653 containerSizer->Add(
m_functionalPanel, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxTOP | wxBOTTOM, 5);
787 LOG_INFO(
"FileExplorer", wxString::Format(
"Adding directory to tree: %s", path).ToStdString());
789 if (!wxDir::Exists(path)) {
790 LOG_ERROR(
"FileExplorer", wxString::Format(
"Directory does not exist: %s", path).ToStdString());
795 if (!dir.IsOpened()) {
796 LOG_ERROR(
"FileExplorer", wxString::Format(
"Cannot open directory: %s", path).ToStdString());
808 std::vector<FileInfo> directories;
809 std::vector<FileInfo> files;
813 bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
816 bool isHidden = filename.StartsWith(
".");
818 wxString fullPath = path + wxFileName::GetPathSeparator() + filename;
823 info.name = filename;
824 info.fullPath = fullPath;
825 info.isDirectory =
true;
828 wxFileName fn(fullPath);
829 fn.GetTimes(
nullptr, &info.modTime,
nullptr);
832 directories.push_back(info);
835 cont = dir.GetNext(&filename);
839 cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES);
842 bool isHidden = filename.StartsWith(
".");
844 wxString fullPath = path + wxFileName::GetPathSeparator() + filename;
847 info.name = filename;
848 info.fullPath = fullPath;
849 info.isDirectory =
false;
852 wxFileName fn(fullPath);
853 fn.GetTimes(
nullptr, &info.modTime,
nullptr);
854 info.size = fn.GetSize();
856 files.push_back(info);
858 cont = dir.GetNext(&filename);
862 auto sortFunc = [
this](
const FileInfo &a,
const FileInfo &b) ->
bool {
866 return a.name.CmpNoCase(b.name) < 0;
868 return a.modTime > b.modTime;
870 return a.size > b.size;
872 wxString extA = wxFileName(a.name).GetExt().Lower();
873 wxString extB = wxFileName(b.name).GetExt().Lower();
876 return a.name.CmpNoCase(b.name) < 0;
879 return a.name.CmpNoCase(b.name) < 0;
882 std::sort(directories.begin(), directories.end(), sortFunc);
883 std::sort(files.begin(), files.end(), sortFunc);
886 for (
const auto &dirInfo : directories) {
896 for (
const auto &fileInfo : files) {
897 wxString displayName =
GetDisplayName(fileInfo.name,
false,
true);
1291 LOG_ERROR(
"FileExplorer",
"Grid control not initialized");
1298 LOG_INFO(
"FileExplorer",
"Populating grid with path: " + path.ToStdString());
1300 if (!wxDir::Exists(path)) {
1301 LOG_ERROR(
"FileExplorer",
"Path does not exist: " + path.ToStdString());
1306 if (!dir.IsOpened()) {
1307 LOG_ERROR(
"FileExplorer",
"Cannot open directory: " + path.ToStdString());
1319 std::vector<FileInfo> directories;
1320 std::vector<FileInfo> files;
1324 bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
1326 bool isHidden = filename.StartsWith(
".");
1328 wxString fullPath = path + wxFileName::GetPathSeparator() + filename;
1331 info.name = filename;
1332 info.fullPath = fullPath;
1333 info.isDirectory =
true;
1335 wxFileName fn(fullPath);
1336 fn.GetTimes(
nullptr, &info.modTime,
nullptr);
1339 directories.push_back(info);
1341 cont = dir.GetNext(&filename);
1345 cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES);
1347 bool isHidden = filename.StartsWith(
".");
1349 wxString fullPath = path + wxFileName::GetPathSeparator() + filename;
1352 info.name = filename;
1353 info.fullPath = fullPath;
1354 info.isDirectory =
false;
1356 wxFileName fn(fullPath);
1357 fn.GetTimes(
nullptr, &info.modTime,
nullptr);
1358 info.size = fn.GetSize();
1360 files.push_back(info);
1362 cont = dir.GetNext(&filename);
1366 auto sortFunc = [
this](
const FileInfo &a,
const FileInfo &b) ->
bool {
1370 return a.name.CmpNoCase(b.name) < 0;
1372 return a.modTime > b.modTime;
1374 return a.size > b.size;
1376 wxString extA = wxFileName(a.name).GetExt().Lower();
1377 wxString extB = wxFileName(b.name).GetExt().Lower();
1380 return a.name.CmpNoCase(b.name) < 0;
1383 return a.name.CmpNoCase(b.name) < 0;
1386 std::sort(directories.begin(), directories.end(), sortFunc);
1387 std::sort(files.begin(), files.end(), sortFunc);
1390 for (
const auto &dirInfo : directories) {
1391 wxString displayName =
GetDisplayName(dirInfo.name,
true,
false);
1396 for (
const auto &fileInfo : files) {
1397 wxString displayName =
GetDisplayName(fileInfo.name,
false,
false);
1398 AddGridItem(displayName, fileInfo.fullPath,
false);
1449 wxPanel *itemPanel =
new wxPanel(
m_gridCtrl, wxID_ANY, wxDefaultPosition, wxSize(panelWidth, panelHeight));
1450 itemPanel->SetBackgroundColour(wxColour(50, 50, 50));
1451 itemPanel->SetToolTip(name);
1453 wxBoxSizer *itemSizer =
new wxBoxSizer(wxVERTICAL);
1456 wxString iconType = isDirectory ?
"folder" :
"file";
1458 wxString ext = wxFileName(fullPath).GetExt().Lower();
1461 else if (ext ==
"json")
1463 else if (ext ==
"txt")
1465 else if (ext ==
"png" || ext ==
"jpg" || ext ==
"jpeg")
1467 else if (ext ==
"md" || ext ==
"markdown")
1468 iconType =
"markdown";
1473 wxStaticBitmap *iconCtrl =
new wxStaticBitmap(itemPanel, wxID_ANY, icon);
1474 iconCtrl->SetToolTip(name);
1475 itemSizer->Add(iconCtrl, 0, wxALIGN_CENTER | wxALL, 5);
1478 int labelWidth = panelWidth - 10;
1479 wxStaticText *label =
new wxStaticText(itemPanel, wxID_ANY, name, wxDefaultPosition, wxSize(labelWidth, -1),
1480 wxST_ELLIPSIZE_END | wxALIGN_CENTER_HORIZONTAL);
1481 label->SetForegroundColour(wxColour(200, 200, 200));
1484 wxFont labelFont = label->GetFont();
1487 labelFont = labelFont.Smaller();
1493 labelFont = labelFont.Larger();
1495 label->SetFont(labelFont);
1496 label->SetToolTip(name);
1497 itemSizer->Add(label, 0, wxALIGN_CENTER | wxALL, 2);
1499 itemPanel->SetSizer(itemSizer);
1528 gridItem.
name = name;
1531 gridItem.
widget = itemPanel;
2087 if (item && item->IsWindow()) {
2088 wxButton *btn =
dynamic_cast<wxButton *
>(item->GetWindow());
2089 if (btn && btn->GetClientData()) {
2090 wxString *pathPtr =
static_cast<wxString *
>(btn->GetClientData());
2093 btn->SetClientData(
nullptr);
2106 relativePath = relativePath.Mid(
m_rootPath.Length());
2107 if (relativePath.StartsWith(wxFileName::GetPathSeparator())) {
2108 relativePath = relativePath.Mid(1);
2114 new wxButton(
m_breadcrumbPanel, wxID_ANY,
"Resources", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
2115 rootBtn->SetBackgroundColour(wxColour(60, 60, 60));
2116 rootBtn->SetForegroundColour(wxColour(200, 200, 200));
2117 rootBtn->SetMinSize(wxSize(-1, 32));
2118 wxFont rootFont = rootBtn->GetFont();
2119 rootFont.SetPointSize(rootFont.GetPointSize() + 1);
2120 rootBtn->SetFont(rootFont);
2121 rootBtn->SetClientData(
new wxString(
m_rootPath));
2126 if (!relativePath.IsEmpty()) {
2127 wxArrayString pathParts = wxSplit(relativePath, wxFileName::GetPathSeparator());
2130 for (
size_t i = 0; i < pathParts.GetCount(); i++) {
2131 if (pathParts[i].IsEmpty())
2136 separator->SetForegroundColour(wxColour(150, 150, 150));
2137 wxFont sepFont = separator->GetFont();
2138 sepFont.SetPointSize(sepFont.GetPointSize() + 1);
2139 separator->SetFont(sepFont);
2143 currentPath += wxFileName::GetPathSeparator() + pathParts[i];
2144 wxButton *dirBtn =
new wxButton(
m_breadcrumbPanel, wxID_ANY, pathParts[i], wxDefaultPosition, wxDefaultSize,
2146 dirBtn->SetBackgroundColour(wxColour(60, 60, 60));
2147 dirBtn->SetForegroundColour(wxColour(200, 200, 200));
2148 dirBtn->SetMinSize(wxSize(-1, 32));
2149 wxFont dirFont = dirBtn->GetFont();
2150 dirFont.SetPointSize(dirFont.GetPointSize() + 1);
2151 dirBtn->SetFont(dirFont);
2152 dirBtn->SetClientData(
new wxString(currentPath));
3336 auto FormatShortPath = [](
const wxString &fullPath) -> wxString {
3337 wxFileName pathFn(fullPath);
3338 wxArrayString dirs = pathFn.GetDirs();
3339 wxString filename = pathFn.GetFullName();
3341 wxString result =
"..";
3343 if (!filename.IsEmpty()) {
3345 int startIdx = std::max(0, (
int)dirs.GetCount() - 2);
3346 for (
size_t i = startIdx; i < dirs.GetCount(); i++) {
3347 result +=
"/" + dirs[i];
3349 result +=
"/" + filename;
3350 }
else if (dirs.GetCount() > 0) {
3352 int startIdx = std::max(0, (
int)dirs.GetCount() - 3);
3353 for (
size_t i = startIdx; i < dirs.GetCount(); i++) {
3354 result +=
"/" + dirs[i];
3372 wxBoxSizer *mainSizer =
new wxBoxSizer(wxVERTICAL);
3375 wxPanel *activityHeaderPanel =
new wxPanel(
m_historyPanel, wxID_ANY);
3376 activityHeaderPanel->SetBackgroundColour(wxColour(50, 50, 50));
3377 wxBoxSizer *activityHeaderSizer =
new wxBoxSizer(wxHORIZONTAL);
3379 wxStaticText *activityHeader =
new wxStaticText(activityHeaderPanel, wxID_ANY,
"Activity Log");
3380 activityHeader->SetForegroundColour(wxColour(200, 200, 200));
3381 wxFont activityHeaderFont = activityHeader->GetFont();
3382 activityHeaderFont.SetWeight(wxFONTWEIGHT_BOLD);
3383 activityHeader->SetFont(activityHeaderFont);
3384 activityHeaderSizer->Add(activityHeader, 0, wxALL, 8);
3386 activityHeaderPanel->SetSizer(activityHeaderSizer);
3387 mainSizer->Add(activityHeaderPanel, 0, wxEXPAND);
3390 wxScrolledWindow *activityScrollWindow =
new wxScrolledWindow(
m_historyPanel, wxID_ANY);
3391 activityScrollWindow->SetBackgroundColour(wxColour(45, 45, 45));
3392 activityScrollWindow->SetScrollRate(0, 10);
3394 wxBoxSizer *activityContentSizer =
new wxBoxSizer(wxVERTICAL);
3400 for (
size_t i = 0; i < activityCount; i++) {
3403 wxPanel *activityPanel =
new wxPanel(activityScrollWindow, wxID_ANY);
3404 activityPanel->SetBackgroundColour(wxColour(45, 45, 45));
3405 wxBoxSizer *activitySizer =
new wxBoxSizer(wxHORIZONTAL);
3408 wxString actionLabel;
3409 wxColour labelColor;
3410 switch (activity.type) {
3412 actionLabel =
"[NAVIGATE]";
3413 labelColor = wxColour(100, 150, 200);
3416 actionLabel =
"[OPEN]";
3417 labelColor = wxColour(150, 200, 150);
3420 actionLabel =
"[CREATE]";
3421 labelColor = wxColour(100, 200, 100);
3424 actionLabel =
"[DELETE FILE]";
3425 labelColor = wxColour(200, 100, 100);
3428 actionLabel =
"[DELETE FOLDER]";
3429 labelColor = wxColour(200, 100, 100);
3432 actionLabel =
"[RENAME FILE]";
3433 labelColor = wxColour(200, 180, 100);
3436 actionLabel =
"[RENAME FOLDER]";
3437 labelColor = wxColour(200, 180, 100);
3440 actionLabel =
"[COPY]";
3441 labelColor = wxColour(150, 150, 200);
3444 actionLabel =
"[CUT]";
3445 labelColor = wxColour(200, 150, 100);
3448 actionLabel =
"[PASTE]";
3449 labelColor = wxColour(150, 200, 200);
3453 wxStaticText *actionText =
new wxStaticText(activityPanel, wxID_ANY, actionLabel);
3454 actionText->SetForegroundColour(labelColor);
3455 wxFont actionFont = actionText->GetFont();
3456 actionFont.SetWeight(wxFONTWEIGHT_BOLD);
3457 actionText->SetFont(actionFont);
3458 activitySizer->Add(actionText, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 10);
3461 wxString displayText;
3464 if (!activity.details.IsEmpty() &&
3468 int arrowPos = activity.details.Find(
" -> ");
3469 if (arrowPos != wxNOT_FOUND) {
3470 wxString beforePath = activity.details.Mid(0, arrowPos);
3471 wxString afterPart = activity.details.Mid(arrowPos + 8);
3474 wxString afterPath = afterPart;
3475 wxString suffix =
"";
3476 int suffixPos = afterPart.Find(
" (");
3477 if (suffixPos != wxNOT_FOUND) {
3478 afterPath = afterPart.Mid(0, suffixPos);
3479 suffix = afterPart.Mid(suffixPos);
3482 displayText = FormatShortPath(beforePath) +
" -> " + FormatShortPath(afterPath) + suffix;
3484 displayText = activity.details;
3488 displayText = FormatShortPath(activity.path);
3492 int displayArrowPos = displayText.Find(
" -> ");
3493 if (displayArrowPos != wxNOT_FOUND) {
3495 wxString beforeText = displayText.Mid(0, displayArrowPos);
3496 wxString afterText = displayText.Mid(displayArrowPos + 8);
3499 wxBoxSizer *textSizer =
new wxBoxSizer(wxHORIZONTAL);
3502 wxStaticText *textBefore =
new wxStaticText(activityPanel, wxID_ANY, beforeText);
3503 textBefore->SetForegroundColour(wxColour(200, 200, 200));
3504 textSizer->Add(textBefore, 0, wxALIGN_CENTER_VERTICAL);
3507 wxStaticText *arrowText =
new wxStaticText(activityPanel, wxID_ANY,
" -> ");
3508 arrowText->SetForegroundColour(wxColour(100, 255, 100));
3509 textSizer->Add(arrowText, 0, wxALIGN_CENTER_VERTICAL);
3512 wxStaticText *textAfter =
new wxStaticText(activityPanel, wxID_ANY, afterText);
3513 textAfter->SetForegroundColour(wxColour(200, 200, 200));
3514 textSizer->Add(textAfter, 0, wxALIGN_CENTER_VERTICAL);
3516 activitySizer->Add(textSizer, 1, wxEXPAND | wxRIGHT, 10);
3519 wxStaticText *text =
new wxStaticText(activityPanel, wxID_ANY, displayText, wxDefaultPosition,
3520 wxDefaultSize, wxST_NO_AUTORESIZE);
3521 text->SetForegroundColour(wxColour(200, 200, 200));
3523 activitySizer->Add(text, 1, wxEXPAND | wxRIGHT, 10);
3527 wxString timeStr = activity.timestamp.Format(
"%H:%M:%S");
3528 wxStaticText *timeText =
new wxStaticText(activityPanel, wxID_ANY, timeStr);
3529 timeText->SetForegroundColour(wxColour(120, 120, 120));
3530 wxFont timeFont = timeText->GetFont();
3531 timeFont.SetPointSize(timeFont.GetPointSize() - 1);
3532 timeText->SetFont(timeFont);
3533 activitySizer->Add(timeText, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10);
3535 activityPanel->SetSizer(activitySizer);
3536 activityPanel->Layout();
3537 activityPanel->Fit();
3539 wxString tooltip = activity.details.IsEmpty() ? activity.path : activity.details;
3540 activityPanel->SetToolTip(tooltip);
3544 wxString pathCopy = activity.path;
3546 activityPanel->Bind(wxEVT_ENTER_WINDOW, [](wxMouseEvent &evt) {
3547 wxWindow *win =
dynamic_cast<wxWindow *
>(evt.GetEventObject());
3549 win->SetBackgroundColour(wxColour(60, 80, 110));
3554 activityPanel->Bind(wxEVT_LEAVE_WINDOW, [](wxMouseEvent &evt) {
3555 wxWindow *win =
dynamic_cast<wxWindow *
>(evt.GetEventObject());
3557 win->SetBackgroundColour(wxColour(45, 45, 45));
3563 activityPanel->Bind(wxEVT_LEFT_DOWN, [
this, pathCopy](wxMouseEvent &) {
3572 activityContentSizer->Add(activityPanel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 3);
3576 wxStaticText *emptyText =
3577 new wxStaticText(activityScrollWindow, wxID_ANY,
"No activities yet.\nPerform actions to see history.");
3578 emptyText->SetForegroundColour(wxColour(150, 150, 150));
3579 activityContentSizer->Add(emptyText, 0, wxALL | wxALIGN_CENTER, 20);
3583 activityScrollWindow->SetSizer(activityContentSizer);
3584 activityScrollWindow->FitInside();
3587 mainSizer->Add(activityScrollWindow, 1, wxEXPAND);