diff --git a/README.md b/README.md index a255a2e..81a4d7c 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,43 @@ This text Russian language. Размер icon 32х32 2. Задать для сервера цвет. Фон icon будет окрашен в цвет сервера. - +19.08.2021 + - добавлено окно просмотра CSV лога базы. + Окно вызывается из контекстного меню сервера "Log view ...". + + После открытия окна читается непосредственно файл лога функцией pg_read_binary_file + Выбирается файл с самой свежей датой изменения. Проверка новых сообщений проводиться каждые 5 секунд. + Можно добавить другие сервера на панели "Settings". Настройки применяются после закрытия окна и повторного его открытия. + Если окно лога не активное и приходит сообщения уровня Error и выше, то иконка отмечается красным квадратом. + Если на заладке "Settings" выбрано несколько серверов, то происходить автоматическое подключение к ним. + После подключения все открытые сервера в дереве объектов можно закрыть одной командой контекстного меню + "Disconnect all servers". + ВНИМАНИЕ: память требуемая для хранения логов ни чем не ограничивается (кроме фильтрации на этапе загрузки лога) и + возможно выделения большого количества памяти. + Отображаются строки лога в двух режимах: + * Простой. Отобразаются все полученные строки лога + * Групповой. Строки с похожими сообщениями объединяются в группу и видимой строкой является самая последняя строка + в группе. Для просмотра всех строк группы нужно установить флаг "View detail group". + Сообщения будут похожими если они отличаются только числами и если они не в двойных кавычках. + В групповом режиме в поле host показываются счетчик свежих сообщений попавших в группу. Счетчик сбрасывается при + установке курсора на строку группы. + Для исключения из просмотра ненужных строк используются поколоночные фильтры. Для включения фильтра нужно: + * Щелкнуть правой кнопкой мыши по полю. Для инверсии фильтра нужно удерживать Ctrl. + * Выбрать значение в контекстном меню заголовка колонки. Там отображаются 20 самых частых значения в колонке с указанием + количества этих значений. + * Ввести в поле значения для фильтра, выделить это значение и нажать Enter. Для фильтра используется только выделенный + текст. Такой фильтр будет работать на поиск выделенного вхождения в поле. Если в выделенной строке + первым символом будет "!" то фильтер инверсируется. + * каждое отдельное значение фильтра можно удалить через контекстное меню заголовка колонки. + Для более высокой производительности рекомендуется проводить загрузку логов с включенным "Mode group". + Или сбрасывать "Mode group", но при установленных фильтрах. + Отображение большого число строк (более 10000 ) происходит несколько секунд и более. + * Есть возможность отсеять строки на этапе загрузки. Для этого установите фильтры на строки и нажмите + "Add Filter Ignore" этот фильтр будет записан в файл filter_load.txt. + +13.09.2021 + - Добавлено меню закрытия всех открытых серверов "Disconnect all servers" + - + diff --git a/db/pgConn.cpp b/db/pgConn.cpp index 8ecb72d..f99a8a6 100644 --- a/db/pgConn.cpp +++ b/db/pgConn.cpp @@ -548,7 +548,8 @@ bool pgConn::HasFeature(int featureNo, bool forceCheck) wxT(" WHERE proname IN ('pg_tablespace_size', 'pg_file_read', 'pg_logfile_rotate',") wxT( " 'pg_postmaster_starttime', 'pg_terminate_backend', 'pg_reload_conf',") wxT( " 'pgstattuple', 'pgstatindex','bt_index_parent_check')\n") - wxT(" AND nspname IN ('pg_catalog', 'public')"); + wxT(" AND nspname IN ('pg_catalog', 'public')") + wxT(" union all select current_setting('log_destination'),555,null,null,null"); pgSet *set = ExecuteSet(sql); @@ -579,7 +580,8 @@ bool pgConn::HasFeature(int featureNo, bool forceCheck) features[FEATURE_PGSTATINDEX] = true; else if (proname == wxT("bt_index_parent_check") && pronargs == 2 ) features[FEATURE_PGCHECKINDEX] = true; - + else if (proname == wxT("csvlog") && pronargs == 555) + features[FEATURE_CSVLOG] = true; set->MoveNext(); } delete set; diff --git a/frm/frmLog.cpp b/frm/frmLog.cpp new file mode 100644 index 0000000..a2d9165 --- /dev/null +++ b/frm/frmLog.cpp @@ -0,0 +1,638 @@ + +#include "pgAdmin3.h" + +// wxWindows headers +#include +#include +#include +#include +#include +#include + +// wxAUI +#include + +// App headers +#include "frm/frmMain.h" +#include "frm/frmLog.h" +#include "db/pgConn.h" +#include "utils/pgfeatures.h" +#include "schema/pgServer.h" +#include "schema/pgUser.h" +#include "ctl/ctlMenuToolbar.h" +#include "ctl/ctlAuiNotebook.h" +#include "utils/csvfiles.h" +#include "log/StorageModel.h" + +#include + + + + +WX_DEFINE_OBJARRAY(RemoteConnArray2); + + +wxBEGIN_EVENT_TABLE(frmLog, pgFrame) +// test +EVT_CHECKBOX(ID_SET_GROUP, frmLog::OnSetGroup) +EVT_CHECKBOX(ID_SET_DETAILGROUP, frmLog::OnSetDetailGroup) +EVT_BUTTON(ID_CLEAR_ALL_FILTER, frmLog::OnClearAllFilter) +EVT_BUTTON(ID_ADD_FILTER, frmLog::OnAddFilterIgnore) +EVT_SET_FOCUS(frmLog::OnSetFocus) +EVT_KILL_FOCUS(frmLog::OnKillFocus) +EVT_ACTIVATE(frmLog::OnActivate) +wxEND_EVENT_TABLE() + +//#include +//WX_DEFINE_OBJARRAY(RemoteConnArray); + + +void frmLog::OnActivate(wxActivateEvent& event) { + m_storage_model->getStorage()->SetErrMsgFlag(false); + seticon(false); + event.Skip(); +} +void frmLog::OnSetFocus(wxFocusEvent& event) { + m_storage_model->getStorage()->SetErrMsgFlag(false); + seticon(false); +} +void frmLog::OnKillFocus(wxFocusEvent& event) { + m_storage_model->getStorage()->SetErrMsgFlag(false); + seticon(false); + +} + +// Class declarations +void frmLog::OnClearAllFilter(wxCommandEvent& event) { + + my_view->ClearAllFilter(); +} +void frmLog::OnAddFilterIgnore(wxCommandEvent& event) { + + my_view->AddFilterIgnore(); +} +void frmLog::OnSetGroup(wxCommandEvent& event) +{ + //wxDataViewColumn* const col = m_ctrl[Page_List]->GetColumn(0); + if (event.IsChecked()) + { + //wxLogMessage("Group set check"); + my_view->setGroupMode(true); + detail->SetValue(false); + } + else { + //wxLogMessage("Group unset check"); + my_view->setGroupMode(false); + } + detail->Enable(event.IsChecked()); +} +bool frmLog::CheckConn(wxString host,int port) { + for (size_t i = 0; i < conArray.GetCount(); i++) + { + if ((conArray[i].conn->GetHostName() == host)&&(conArray[i].conn->GetPort() == port)) + return true; + } + return false; +} +pgConn* frmLog::createConn(pgServer* srv) { + pgConn* conn; + + if (!srv->GetConnected()) mainForm->ReconnectServer(srv, false); + conn = srv->CreateConn(wxEmptyString, 0, "Log conn"); + + return conn; +} +void frmLog::AddNewConn(pgConn* con) { + if (con != NULL) { + if (!con->HasFeature(FEATURE_CSVLOG)) return; + logfileName.Add(""); + savedPartialLine.Add(""); + logfileLength.Add(0); + len.Add(0); + conArray.Add(new RemoteConn2(con)); + } +} +void frmLog::OnSetDetailGroup(wxCommandEvent& event) +{ + //wxDataViewColumn* const col = m_ctrl[Page_List]->GetColumn(0); + if (event.IsChecked()) + { + my_view->ViewGroup(true); + + } + else { + my_view->ViewGroup(false); + } +} + +void frmLog::getFilename() { + pgSet* set; + wxString namepage; + for (size_t i = 0;i< conArray.GetCount(); i++) { + + + if (!conArray[i].conn->IsAlive()) continue; + set = conArray[i].conn->ExecuteSet( + wxT("select current_setting('log_directory')||'/'||name filename,modification filetime,size len\n") + wxT(" FROM pg_ls_logdir() where name ~ '.csv' ORDER BY modification DESC")); + if (set) + { + + //logfileTimestamp = set->GetDateTime(wxT("filetime")); + len[i] = set->GetLong(wxT("len")); + if (!namepage.IsEmpty()) namepage += ","; + namepage += conArray[i].conn->GetDbname(); + m_storage_model->getStorage()->SetHost(conArray[i].conn->GetHostName()); + + wxString fn = set->GetVal(wxT("filename")); + if (fn != logfileName[i]) { + logfileLength[i] = 0; + logfileName[i] = fn; + + } + /// addLogFile(logfileName, logfileTimestamp, len, logfileLength, skipFirst); + + delete set; + readLogFile(logfileName[i],len[i],logfileLength[i],savedPartialLine[i],conArray[i].conn); + } + } + if (namepage.IsEmpty()) namepage = "not connect"; + if (m_notebook->GetPageText(0) != namepage) m_notebook->SetPageText(0, namepage); +} +void frmLog::readLogFile(wxString logfileName,long& lenfile,long& logfileLength,wxString& savedPartialLine,pgConn* conn) { + wxString line; + + // If GPDB 3.3 and later, log is normally in CSV format. Let's get a whole log line before calling addLogLine, + // so we can do things smarter. + + // PostgreSQL can log in CSV format, as well as regular format. Normally, we'd only see + // the regular format logs here, because pg_logdir_ls only returns those. But if pg_logdir_ls is + // changed to return the csv format log files, we should handle it. + + bool csv_log_format = logfileName.Right(4) == wxT(".csv"); + + if (csv_log_format && savedPartialLine.length() > 0) + { + if (logfileLength == 0) // Starting at beginning of log file + savedPartialLine.clear(); + else + line = savedPartialLine; + } + wxString funcname = "pg_read_binary_file("; + while (lenfile > logfileLength) + { + //statusBar->SetStatusText(_("Reading log from server...")); + pgSet* set = conn->ExecuteSet(wxT("SELECT ") + funcname + + conn->qtDbString(logfileName) + wxT(", ") + NumToStr(logfileLength) + wxT(", 50000)")); + if (!set) + { + conn->IsAlive(); + return; + } + char* raw1 = set->GetCharPtr(0); + + if (!raw1 || !*raw1) + { + delete set; + break; + } + char* raw; + unsigned char m[50001]; + if (settings->GetASUTPstyle()) { + + raw = (char*)&m[0]; + unsigned char c; + unsigned char* startChar; + int pos = 0; + raw1 = raw1 + 2; + int utf8charLen = 0; + while (*raw1 != 0) { + c = *raw1; + c = c - '0'; + if (c > 9) c = *raw1 - 'a' + 10; + raw1++; + m[pos] = c << 4; + c = *raw1 - '0'; + if (c > 9) c = *raw1 - 'a' + 10; + c = c | m[pos]; + m[pos] = c; + // check utf-8 char + if (utf8charLen == 0) { + startChar = &m[pos]; + if (c >> 7 == 0) + utf8charLen = 1; + else if (c >> 5 == 0x6) + utf8charLen = 2; + else if (c >> 4 == 0xE) + utf8charLen = 3; + else if (c >> 5 == 0x1E) + utf8charLen = 4; + else + utf8charLen = 0; + // bad utf8 format + } + pos++; + raw1++; + utf8charLen--; + } + // + if (utf8charLen != 0) { + //read = startChar - &m[0]; + // remove bad utf-8 char + *startChar = 0; + } + else + m[pos] = 0; + } + else { + raw = raw1; + } + int l= strlen(raw); + logfileLength += l; + status->SetLabelText(wxString::Format("Load bytes %ld", logfileLength)); + wxString str; + str = line + wxTextBuffer::Translate(wxString(raw, set->GetConversion()), wxTextFileType_Unix); + //if (wxString(wxString(raw, wxConvLibc).wx_str(), wxConvUTF8).Len() > 0) + // str = line + wxString(wxString(raw, wxConvLibc).wx_str(), wxConvUTF8); + //else { + // str = line + wxTextBuffer::Translate(wxString(raw, set->GetConversion()), wxTextFileType_Unix); + //} + + + delete set; + + if (str.Len() == 0) + { + wxString msgstr = _("The server log contains entries in multiple encodings and cannot be displayed by pgAdmin."); + wxMessageBox(msgstr); + return; + } + + if (csv_log_format) + { + // This will work for any DB using CSV format logs + + + CSVLineTokenizer tk(str); + + + my_view->Freeze(); + while (tk.HasMoreLines()) + { + line.Clear(); + + bool partial; + str = tk.GetNextLine(partial); + if (partial) + { + line = str; // Start of a log line, but not complete. Loop back, Read more data. + break; + } + + + // Looks like we have a good complete CSV log record. + + //addLogLine(str.Trim(), true, true); + my_view->AddRow(str.Trim()); + } + + my_view->Thaw(); + } + else + { + } + } + + savedPartialLine.clear(); + + if (!line.IsEmpty()) + { + // We finished reading to the end of the log file, but still have some data left + if (csv_log_format) + { + savedPartialLine = line; // Save partial log line for next read of the data file. + line.Clear(); + } + else + my_view->AddRow(line.Trim()); + } + + +} +void frmLog::OnTimer(wxTimerEvent& event) { + Storage* st = m_storage_model->getStorage(); + //int rows = st->getCountStore(); + int ra, ri; + st->ClearRowsStat(); + getFilename(); + st->GetRowsStat(ra, ri); + //int newrows = st->getCountStore(); + //if (loglen !=logfileLength) + if (m_storage_model->getStorage()->GetErrMsgFlag()) { + seticon(true); + } + status->SetLabelText(wxString::Format("Add rows %d ignore %d. View rows %d", ra,ri, m_storage_model->GetRowCount()) ); + +} +#include "log/log_xpm.xpm" +#include "log/log_red_xpm.xpm" +void frmLog::seticon(bool errflag) { + //wxImage img = *sql_32_png_img; + if (errflag) { + SetIcon(idefRed); + } + else { + SetIcon(idef); + } + return; + + wxBitmap* b = new wxBitmap(log_xpm); + + //wxIcon ico=img. + wxMemoryDC dc(*b); + dc.SetBrush(*wxYELLOW_BRUSH); + + //dc.SetBackground(*wxYELLOW_BRUSH); + dc.SetBackground(*wxRED_BRUSH); + dc.SetBrush(*wxRED_BRUSH); + + //dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetTextForeground(*wxRED); + dc.SetPen(*wxRED_PEN); + wxRect rect(7,4,7, 7); + + if (errflag) dc.DrawRoundedRectangle(rect, 0); + +// wxFont font = wxFont(5, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); +// wxFontStyle s; +// font.SetStyle(wxFONTFLAG_ANTIALIASED); +// dc.SetFont(font); + + wxImage img = b->ConvertToImage(); + dc.SelectObject(wxNullBitmap); + int w = img.GetWidth(); + int h = img.GetHeight(); + wxColor p; + wxColor c= wxColor(0,255,0); + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) { + p = wxColour(img.GetRed(x, y), + img.GetGreen(x, y), + img.GetBlue(x, y)); + //if (img.IsTransparent(x, y)) img.SetRGB(x, y, 255, 255, 255); + if (p == c) { + //img.SetTra + img.SetAlpha(x, y, 255); + img.SetRGB(x, y, img.GetMaskRed(), img.GetMaskGreen(), img.GetMaskBlue()); + ; + }; + } + + //wxIcon* ico = new wxIcon(); + wxIcon ico=GetIcon(); + wxBitmap* bmp = new wxBitmap(img); + ico.CopyFromBitmap(*bmp); + //SetIcon(*sql_32_png_ico); + + SetIcon(ico); + + +} +frmLog::frmLog(frmMain *form, const wxString &_title, pgServer *srv) : pgFrame(NULL, _title) +{ + + dlgName = wxT("frmLog"); + RestorePosition(-1, -1, 700, 500, 700, 500); + //SetIcon(wxIcon(log_xpm)); + idef = wxIcon(log_xpm); + idefRed = wxIcon(log_red_xpm); + seticon(false); + mainForm = form; + + m_notebook = new wxNotebook( this, wxID_ANY ); + + wxPanel* testPanel = new wxPanel(m_notebook, wxID_ANY); + //BuildDataViewCtrl(testPanel, Page_Test); + my_view = new MyDataViewCtrl(testPanel, wxID_ANY, wxDefaultPosition, + wxDefaultSize, wxDV_VARIABLE_LINE_HEIGHT | wxDV_HORIZ_RULES | wxDV_VERT_RULES); + my_view->GetMainWindow()->Bind(wxEVT_MOTION, &MyDataViewCtrl::OnMouseMove, my_view); + my_view->GetMainWindow()->Bind(wxEVT_KEY_DOWN, &MyDataViewCtrl::OnKEY_DOWN, my_view); + my_view->GetMainWindow()->Bind(wxEVT_KEY_UP, &MyDataViewCtrl::OnKEY_DOWN, my_view); + my_view->Bind(wxEVT_DATAVIEW_COLUMN_HEADER_CLICK, &MyDataViewCtrl::OnEVT_DATAVIEW_COLUMN_HEADER_CLICK, my_view); + my_view->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, &MyDataViewCtrl::OnEVT_DATAVIEW_SELECTION_CHANGED, my_view); + my_view->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &MyDataViewCtrl::OnContextMenu, my_view); + + // my_view->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &MyDataViewCtrl::OnContextMenu, my_view); + + m_timer.Bind(wxEVT_TIMER, &frmLog::OnTimer, this); + + + + + my_view->Bind(wxEVT_MENU, &MyDataViewCtrl::OnEVT_DATAVIEW_CONTEXT_MENU, my_view); + m_storage_model = new StorageModel(my_view); + my_view->AssociateModel(m_storage_model.get()); + + m_storage_model->BuildColumns(my_view); + wxString s; + s=settings->Read(dlgName + "/ColsWidth",""); + if (s.Len()>0) my_view->setSettingString(s); + //settings->Write(dlgName + "/ColsWidth", s); + + wxSizer* zeroPanelSz = new wxBoxSizer(wxVERTICAL); + my_view->SetMinSize(wxSize(-1, 200)); + zeroPanelSz->Add(my_view, 1, wxGROW | wxALL, 5); + + status = new wxStaticText(testPanel, wxID_ANY, "status text"); + zeroPanelSz->Add( + status, + 0, wxGROW | wxALL, 5); + //zeroPanelSz->Add(button_sizer); + //zeroPanelSz->Add(sizerCurrent); + my_view->setStatusObj(status); + wxBoxSizer* sSizer = new wxBoxSizer(wxHORIZONTAL); + group = new wxCheckBox(testPanel, ID_SET_GROUP, "Mode group"); + sSizer->Add(group, + wxSizerFlags().Centre().DoubleBorder()); + detail = new wxCheckBox(testPanel, ID_SET_DETAILGROUP, "View detail group"); + sSizer->Add(detail, + wxSizerFlags().Centre().DoubleBorder()); + const wxSizerFlags border1 = wxSizerFlags().DoubleBorder(); + + //wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL); + sSizer->Add(new wxButton(testPanel, ID_CLEAR_ALL_FILTER, "Clear All Filter"), border1); + //sSizer->Add(new wxButton(testPanel, ID_DELETE_SEL, "Delete selected"), border); + sSizer->Add(new wxButton(testPanel, ID_ADD_FILTER, "Add Filter Ignore"), border1); + + + zeroPanelSz->Add(sSizer); + testPanel->SetSizerAndFit(zeroPanelSz); + + m_notebook->AddPage(testPanel, "Log"); + wxPanel* settingPanel = new wxPanel(m_notebook, wxID_ANY); + lb = new wxCheckListBox(settingPanel,wxID_ANY); + wxTreeItemIdValue foldercookie, servercookie; + wxTreeItemId folderitem, serveritem; + pgObject* object; + pgServer* server; + std::vector vec; + folderitem = mainForm->GetBrowser()->GetFirstChild(mainForm->GetBrowser()->GetRootItem(), foldercookie); + while (folderitem) + { + if (mainForm->GetBrowser()->ItemHasChildren(folderitem)) + { + serveritem = mainForm->GetBrowser()->GetFirstChild(folderitem, servercookie); + while (serveritem) + { + object = mainForm->GetBrowser()->GetObject(serveritem); + if (object && object->IsCreatedBy(serverFactory)) + { + server = (pgServer*)object; + wxString srvname = wxString::Format("%s:%d", server->GetName(), server->GetPort()); + vec.push_back(srvname); + + } + serveritem = mainForm->GetBrowser()->GetNextChild(folderitem, servercookie); + } + } + folderitem = mainForm->GetBrowser()->GetNextChild(mainForm->GetBrowser()->GetRootItem(), foldercookie); + } + lb->Append(vec); + wxString srvs; + wxString srvname = wxString::Format("%s:%d", srv->GetName(), srv->GetPort()); + settings->Read(dlgName + "/AutoConnect", &srvs, ""); + if (!srvs.IsEmpty()) srvs += ";"; + srvs += srvname; + wxStringTokenizer tk(srvs, ";", wxTOKEN_RET_EMPTY_ALL); + while (tk.HasMoreTokens()) + { + wxString l = tk.GetNextToken(); + pgServer* s = getServer(l); + if (s != NULL) { + if (!CheckConn(s->GetName(), s->GetPort())) { + pgConn* conn = createConn(s); + AddNewConn(conn); + for (unsigned int x = 0; x < lb->GetCount(); x++) + if (l==lb->GetString(x)) lb->Check(x, true); + + } + } + + } + + wxSizer* zeroPanelSz2 = new wxBoxSizer(wxVERTICAL); + lb->SetMinSize(wxSize(-1, 200)); + zeroPanelSz2->Add(lb, 1, wxGROW | wxALL, 5); + settingPanel->SetSizerAndFit(zeroPanelSz2); + m_notebook->AddPage(settingPanel, "Settings"); + + bool b=true; + settings->Read(dlgName + "/Mode",&b, false); + group->SetValue(b); + my_view->setGroupMode(b); +// if (mainForm) getFilename(); + m_timer.Start(timerInterval); +} +pgServer* frmLog::getServer(wxString& strserver) { + wxTreeItemIdValue foldercookie, servercookie; + wxTreeItemId folderitem, serveritem; + pgObject* object; + pgServer* server; + folderitem = mainForm->GetBrowser()->GetFirstChild(mainForm->GetBrowser()->GetRootItem(), foldercookie); + while (folderitem) + { + if (mainForm->GetBrowser()->ItemHasChildren(folderitem)) + { + serveritem = mainForm->GetBrowser()->GetFirstChild(folderitem, servercookie); + while (serveritem) + { + object = mainForm->GetBrowser()->GetObject(serveritem); + if (object && object->IsCreatedBy(serverFactory)) + { + server = (pgServer*)object; + wxString srvname = wxString::Format("%s:%d", server->GetName(), server->GetPort()); + if (srvname == strserver) { + return server; + } + } + serveritem = mainForm->GetBrowser()->GetNextChild(folderitem, servercookie); + } + } + folderitem = mainForm->GetBrowser()->GetNextChild(mainForm->GetBrowser()->GetRootItem(), foldercookie); + } + return 0; +} + +frmLog::~frmLog() +{ + // If the status window wasn't launched in standalone mode... + if (mainForm) + mainForm->RemoveFrame(this); + + + // If connection is still available, delete it + SavePosition(); + wxString srvs; + for (unsigned int x = 0; x < lb->GetCount(); x++) + if (lb->IsChecked(x)) { + if (!srvs.IsEmpty()) srvs += ";"; + srvs += lb->GetString(x); + } + settings->Write(dlgName + "/AutoConnect", srvs); + wxString s = my_view->getSettingString(); + settings->Write(dlgName+"/ColsWidth",s); + settings->WriteBool(dlgName + "/Mode", group->IsChecked()); + Storage *st=m_storage_model->getStorage(); + st->saveFilters(); + mainForm->Logfrm = NULL; +} +void frmLog::Go() +{ + // Show the window + Show(true); +} +LogFactory::LogFactory(menuFactoryList* list, wxMenu* mnu, ctlMenuToolbar* toolbar) : contextActionFactory(list) +{ + mnu->Append(id, _("Log view..."), _("Log view CSV format")); +} + + +wxWindow* LogFactory::StartDialog(frmMain* form, pgObject* obj) +{ + pgServer* srv = (pgServer *) obj; + wxString txt = ""; + if (form->Logfrm != NULL) { + if (!form->Logfrm->CheckConn(srv->GetName(), srv->GetPort())) { + pgConn* conn = form->Logfrm->createConn(srv); + form->Logfrm->AddNewConn(conn); + } + } + else { + form->Logfrm = new frmLog(form, txt, srv); + if (form->Logfrm!=NULL) + form->AddFrame(form->Logfrm); + + } + //frmLog* frm = new frmLog(form, obj); + + form->Logfrm->Go(); + return 0; +} + + +bool LogFactory::CheckEnable(pgObject* obj) +{ + if (!obj) + return false; + + if (obj->GetMetaType() == PGM_SERVER) { +// if (!((pgServer*)obj)->GetConnected()) +// return false; + return true; + } + return false; +} + + + + diff --git a/frm/frmMain.cpp b/frm/frmMain.cpp index c9ee036..fd73a37 100644 --- a/frm/frmMain.cpp +++ b/frm/frmMain.cpp @@ -63,6 +63,7 @@ #include "frm/frmReport.h" #include "frm/frmMaintenance.h" #include "frm/frmStatus.h" +#include "frm/frmLog.h" #include "frm/frmPassword.h" #ifdef DATABASEDESIGNER #include "frm/frmDatabaseDesigner.h" @@ -109,7 +110,7 @@ frmMain::frmMain(const wxString &title) lastPluginUtility = NULL; pluginUtilityCount = 0; m_refreshing = false; - + Logfrm = NULL; dlgName = wxT("frmMain"); SetMinSize(wxSize(600, 450)); RestorePosition(50, 50, 750, 550, 600, 450); @@ -375,6 +376,7 @@ void frmMain::CreateMenus() new connectServerFactory(menuFactories, toolsMenu, 0); new disconnectServerFactory(menuFactories, toolsMenu, 0); + new disconnectServerFactoryAll(menuFactories, toolsMenu, 0); new disconnectDatabaseFactory(menuFactories, toolsMenu, 0); new startServiceFactory(menuFactories, toolsMenu, 0); @@ -448,6 +450,7 @@ void frmMain::CreateMenus() new backupFactory(menuFactories, toolsMenu, 0); new backupGlobalsFactory(menuFactories, toolsMenu, 0); + new LogFactory(menuFactories, cfgMenu, 0); new backupServerFactory(menuFactories, toolsMenu, 0); new restoreFactory(menuFactories, toolsMenu, 0); new importFactory(menuFactories, toolsMenu, 0); diff --git a/include/frm/frmLog.h b/include/frm/frmLog.h new file mode 100644 index 0000000..fd856db --- /dev/null +++ b/include/frm/frmLog.h @@ -0,0 +1,134 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin III - PostgreSQL Tools +// +// Copyright (C) 2002 - 2016, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +// frmStatus.h - Status Screen +// +////////////////////////////////////////////////////////////////////////// + +#ifndef __FRMLOG_H +#define __FRMLOG_H + +// wxWindows headers +#include +#include +#include +#include +#include +#include + + +// wxAUI +#include + +#include "dlg/dlgClasses.h" +#include "utils/factory.h" +#include "ctl/ctlAuiNotebook.h" +#include "log/StorageModel.h" +#include "log/MyDataViewCtrl.h" + +class RemoteConn2 +{ +public: + RemoteConn2(pgConn* c) + { + conn = c; + } + ~RemoteConn2() + { + if (conn) delete conn; + } + + pgConn* conn; +}; + +WX_DECLARE_OBJARRAY(RemoteConn2, RemoteConnArray2); + +enum +{ + ID_SET_GROUP = 207, + ID_CLEAR_ALL_FILTER = 208, + ID_SET_DETAILGROUP = 209, + ID_ADD_FILTER = 210, + ID_NEXT_MAX +}; + + + +// +// This number MUST be incremented if changing any of the default perspectives +// +#define FRMLOG_PERSPECTIVE_VER wxT("8274") + +#ifdef __WXMAC__ +#define FRMLOG_DEFAULT_PERSPECTIVE wxT("layout2|name=Activity;caption=Activity;state=6293500;dir=4;layer=0;row=0;pos=0;prop=100000;bestw=321;besth=244;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=462;floaty=165;floatw=595;floath=282|name=Locks;caption=Locks;state=6293500;dir=4;layer=0;row=0;pos=1;prop=100000;bestw=321;besth=244;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-231;floaty=235;floatw=595;floath=282|name=Transactions;caption=Transactions;state=6293500;dir=4;layer=0;row=0;pos=2;prop=100000;bestw=0;besth=0;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=461;floaty=527;floatw=595;floath=282|name=Logfile;caption=Logfile;state=6293500;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=0;besth=0;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-103;floaty=351;floatw=595;floath=282|name=toolBar;caption=Tool bar;state=2124528;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=808;besth=33;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=888;floaty=829;floatw=558;floath=49|dock_size(4,0,0)=583|dock_size(5,0,0)=10|dock_size(1,10,0)=35|") +#else +#ifdef __WXGTK__ +#define FRMLOG_DEFAULT_PERSPECTIVE wxT("layout2|name=Activity;caption=Activity;state=6293500;dir=4;layer=0;row=1;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=174;floaty=216;floatw=578;floath=282|name=Locks;caption=Locks;state=6293500;dir=4;layer=0;row=1;pos=2;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=136;floaty=339;floatw=576;floath=283|name=Transactions;caption=Transactions;state=6293500;dir=4;layer=0;row=1;pos=3;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=133;floaty=645;floatw=577;floath=283|name=Querystate;caption=Query State;state=6309884;dir=4;layer=0;row=1;pos=1;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=154;floaty=255;floatw=1360;floath=751|name=Logfile;caption=Logfile;state=6293500;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1|name=toolBar;caption=toolBar;state=2108144;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=716;besth=23;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=586;floaty=525;floatw=483;floath=49|dock_size(1,10,0)=25|dock_size(4,0,1)=1115|dock_size(5,0,0)=22|") +#else +#define FRMLOG_DEFAULT_PERSPECTIVE wxT("layout2|name=Activity;caption=Activity;state=6293500;dir=4;layer=0;row=1;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=174;floaty=216;floatw=578;floath=282|name=Locks;caption=Locks;state=6293500;dir=4;layer=0;row=1;pos=2;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=136;floaty=339;floatw=576;floath=283|name=Transactions;caption=Transactions;state=6293500;dir=4;layer=0;row=1;pos=3;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=133;floaty=645;floatw=577;floath=283|name=Querystate;caption=Query State;state=6309884;dir=4;layer=0;row=1;pos=1;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=154;floaty=255;floatw=1360;floath=751|name=Logfile;caption=Logfile;state=6293500;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1|name=toolBar;caption=toolBar;state=2108144;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=716;besth=23;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=586;floaty=525;floatw=483;floath=49|dock_size(1,10,0)=25|dock_size(4,0,1)=1115|dock_size(5,0,0)=22|") +#endif +#endif + + + + +class frmLog : public pgFrame +{ +public: + frmLog(frmMain *form, const wxString &_title, pgServer *srv); + ~frmLog(); + void Go(); + void getFilename(); + void readLogFile(wxString logfileName, long& lenfile, long& logfileLength, wxString& savedPartialLine, pgConn* conn); + void AddNewConn(pgConn* con); + pgServer* getServer(wxString& strserver); + pgConn* createConn(pgServer* srv); + bool CheckConn(wxString host, int port); +private: + static const int timerInterval = 5000; // 1000 ms + wxTimer m_timer; + wxAuiManager manager; + + frmMain *mainForm; + pgConn *connection; + RemoteConnArray2 conArray; + MyDataViewCtrl* my_view; + wxNotebook* m_notebook; + wxStaticText* status; + wxCheckBox *group, *detail; + wxCheckListBox* lb; + wxObjectDataPtr m_storage_model; + wxArrayString logfileName; + wxArrayString savedPartialLine; + wxArrayLong logfileLength; + wxArrayLong len; + void OnSetGroup(wxCommandEvent& event); + void OnSetDetailGroup(wxCommandEvent& event); + void OnClearAllFilter(wxCommandEvent& event); + void OnAddFilterIgnore(wxCommandEvent& event); + void OnSetFocus(wxFocusEvent& event); + void OnKillFocus(wxFocusEvent& event); + void OnActivate(wxActivateEvent& event); + void OnTimer(wxTimerEvent& event); + void seticon(bool errflag); + + wxIcon idef; + wxIcon idefRed; + + DECLARE_EVENT_TABLE() +}; + +class LogFactory : public contextActionFactory +{ +public: + LogFactory(menuFactoryList* list, wxMenu* mnu, ctlMenuToolbar* toolbar); + wxWindow* StartDialog(frmMain* form, pgObject* obj); + bool CheckEnable(pgObject* obj); +}; + + +#endif diff --git a/include/frm/frmMain.h b/include/frm/frmMain.h index a75cf7e..cbad701 100644 --- a/include/frm/frmMain.h +++ b/include/frm/frmMain.h @@ -24,6 +24,7 @@ #include "frm/frmQuery.h" #include "dlg/dlgClasses.h" #include "utils/factory.h" +#include "frm/frmLog.h" // // This number MUST be incremented if changing any of the default perspectives @@ -41,6 +42,7 @@ #endif #endif class pgServer; +class Logfrm; class pgServerCollection; class ctlSQLBox; class ctlTree; @@ -94,7 +96,6 @@ class frmMain : public pgFrame public: frmMain(const wxString &title); ~frmMain(); - void OnAction(wxCommandEvent &ev); void OnReport(wxCommandEvent &ev); wxString GetHelpPage() const; @@ -191,12 +192,14 @@ public: void ObjectBrowserRefreshing(bool refresh) { m_refreshing = refresh; - } - + }; + #if defined(HAVE_OPENSSL_CRYPTO) || defined(HAVE_GCRYPT) void OnSSHTunnelEvent(wxCommandEvent &event); #endif + public: + frmLog* Logfrm; private: wxAuiManager manager; ctlTree *browser; diff --git a/include/log/MyDataViewCtrl.h b/include/log/MyDataViewCtrl.h new file mode 100644 index 0000000..0575985 --- /dev/null +++ b/include/log/MyDataViewCtrl.h @@ -0,0 +1,53 @@ +#pragma once +#include "wx\dataview.h" +#include "wx/wx.h" +#include "Storage.h" +class MyDataViewCtrl : + public wxDataViewCtrl +{ +public: + MyDataViewCtrl(wxWindow* parent, wxWindowID id, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, long style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxASCII_STR(wxDataViewCtrlNameStr) + ) : wxDataViewCtrl(parent, id, pos, size, style, validator, name) + { + + + } + void setStatusObj(wxStaticText* o) { + st = o; + } + int GetLastMouseRow() { return lastrow; }; + int GetLastMouseCol() { return lastcol; }; + wxString getSettingString(); + void setSettingString(wxString setstr); + void setGroupMode(bool mode); + void ViewGroup(bool view); + void ClearAllFilter(); + void AddFilterIgnore(); + void AddRow(wxString csvtext); + void OnMouseMove(wxMouseEvent& event); + void OnKEY_DOWN(wxKeyEvent& event); +#ifdef MYTEST + void OnTimer(wxTimerEvent& event); +#endif + + void OnEVT_DATAVIEW_COLUMN_HEADER_CLICK(wxDataViewEvent& event); + void OnEVT_DATAVIEW_CONTEXT_MENU(wxCommandEvent& event); + void OnEVT_DATAVIEW_SELECTION_CHANGED(wxDataViewEvent& event); + void OnContextMenu(wxDataViewEvent& event); + DECLARE_EVENT_TABLE() + +private: + int lastrow = -1, lastcol = 1; + wxDataViewItem selectRowGroup; + bool modctrl = false; + wxStaticText* st; + //bool visibleEndLine = false; +#ifdef MYTEST + int linenumber = -1; + wxArrayString logadd; +#endif +}; diff --git a/include/log/Storage.h b/include/log/Storage.h new file mode 100644 index 0000000..f4761ec --- /dev/null +++ b/include/log/Storage.h @@ -0,0 +1,183 @@ +#pragma once +#include +#include +#include "wx/hashmap.h" +#include "utils/csvfiles.h" +#define MYTEST 1 + +#define FL_REVERSE 1 +#define FL_CONTAINS 2 + +WX_DECLARE_HASH_MAP(int, int, wxIntegerHash, wxIntegerEqual, MyHashToRow); + +namespace MyConst { + enum colField { + logtime, + loguser, + logdb, + logpid, + loghost, + logtag, + logSessiontime, + logSeverity, + logSqlstate, + logMessage, + logDetail, + logHint, + logappname, + logbtype, + Col_Max + }; + enum iconIndex { + log, + war, + user, + error, + fatal, + panic, + MAX_COL + }; + enum ltype { + SIMPLE_TEXT, + InGroup, + Group + }; +} +struct ps { + unsigned short int s; + unsigned short int l; +}; +struct Line { + unsigned short type : 3; + unsigned short icon : 3; + unsigned short visible : 1; + int prevRowGroup = -1; + int hash; + ps logtime = { 0,0 }; + ps loguser = { 0,0 }; + ps logdb = { 0,0 }; + ps logpid = { 0,0 }; + ps loghost = { 0,0 }; + ps logtag = { 0,0 }; + ps logSessiontime = { 0,0 }; + ps logSeverity = { 0,0 }; + ps logSqlstate = { 0,0 }; + ps logMessage = { 0,0 }; + ps logDetail = { 0,0 }; + ps logHint = { 0,0 }; + ps logappname = { 0,0 }; + ps logbtype = { 0,0 }; + wxString text; +}; +struct LineFilter { + int col; + int flags; + wxString val; +}; + +class Storage +{ +public: + bool AddLineTextCSV(const wxString& strcsv); + wxString GetField(int row, MyConst::colField col); + wxString GetFieldStorage(int row, MyConst::colField col, bool filter); + Storage(); + int GetSeverityIndex(int row); + void SetHost(wxString& host) { currhost = host; }; + wxString GetHost() { return currhost; }; + void SetErrMsgFlag(bool flag) { err_msg = flag; }; + bool GetErrMsgFlag() { return err_msg; }; + void GetRowsStat(int& Rowsadd, int& Rowsignore) { + Rowsadd=rowsadd; + Rowsignore=rowsignore; + }; + void ClearRowsStat() { + rowsadd = 0; + rowsignore = 0; + }; + wxColor& GetBgColorLine(int row); + // + int SetFilter(int colfld, wxString& val, int flags); + // + // true () + bool ApplyFilter(int row = -1); + wxString getStrGroup(wxString source); + int testFilter(MyConst::colField col, int position); + wxString GetStringFilterExpr(int positionArrayFilter,bool addNumCol=false); + void addLineFilterStr(wxString strflt); + wxString _strwhere(int flags); + void saveFilters(); + wxString LineFilterToStr(LineFilter& lf); + // + int getHashString(wxString str) { + std::hash string_hash; + return string_hash(str); + } + void DropColFilter(int index); + void setDetailGroupRow(int rowGroup); + bool IsGroupFilter() { + return groupFilterUse; + }; + bool IsFilter() { + return fCol.size() > 0 || IsGroupFilter(); + }; + void setGroupFilter(bool val) { + detailGroup = -1; + groupFilterUse = val; + MyHashToRow::iterator it; + for (it = hashKeyToCount.begin(); it != hashKeyToCount.end(); ++it) + { + int keyhash = it->first, lastrow = it->second; + // do something useful with key and value + it->second = 0; + } + m_cacheIndex = -1; + frows.clear(); + } + void ClearCount(int rowfilter); + bool IsAddGroupNew() { + return faddgroup; + }; + int getLastRowIndex() { return m_cacheIndex; } + // + int getCountStore(); + int getCountFilter(); + int getCountGroup(int row); + int GetTotalCountGroup(int rowfilter); + +private: + bool checkFilter(Line& l); + Line getLineParse(const wxString& str, bool csv = false); + wxString get_field(Line& l, MyConst::colField col); + LineFilter getLineFilter(wxString strflt); + void getLineToCache(int row, bool filter = true); + bool CompareFilterLine(int row, bool filter); + + std::deque storage; + std::deque filterload; + // hash Group to row + MyHashToRow hashKeyToRow; + MyHashToRow hashKeyToCount; + MyHashToRow hashKeyTotal; + //filter setting + std::deque frows; + wxArrayInt fCol; + wxArrayInt fFlags; + wxArrayString fVal; + // . + bool err_msg; + // + bool groupFilterUse = false; + bool faddgroup = false; + int prevRow = -1; + int detailGroup = -1; + // + int m_cacheIndex = -1; + Line m_cacheLine; + // + int rowsadd; + int rowsignore; + wxColor bgErr[MyConst::iconIndex::MAX_COL]; + wxString currhost; +}; + diff --git a/include/log/StorageModel.h b/include/log/StorageModel.h new file mode 100644 index 0000000..a0c54e1 --- /dev/null +++ b/include/log/StorageModel.h @@ -0,0 +1,101 @@ +#pragma once +#include "pgAdmin3.h" +#include "wx/hashmap.h" +#include "wx/vector.h" +#include "Storage.h" +#include "MyDataViewCtrl.h" + + +WX_DECLARE_STRING_HASH_MAP(int, MyHashCount); + +class StorageModel : public wxDataViewVirtualListModel +{ +public: + enum cols + { + Col_ToggleIconText, + Col_LogTime, + Col_User, + Col_Db, + Col_PID, + Col_Host, + Col_App, + Col_Hint, + Col_Detail, + Col_Message, + Col_Max + }; + + StorageModel(MyDataViewCtrl* view); + + // helper methods to change the model + + bool Prepend(const wxString& text); + void DeleteItem(const wxDataViewItem& item); + void DeleteItems(const wxDataViewItemArray& items); + void AddMany(); + bool setFilter(int col, wxString val, int flags, MyDataViewCtrl* view); + void DropColFilter(int index); + int testFilter(int col, int position); + void ApplyFilter(); + bool getGroupFilter() + { + return store->IsGroupFilter(); + } + void setGroupFilter(bool val) + { + store->setGroupFilter(val); + ApplyFilter(); + } + void BuildColumns(MyDataViewCtrl* ctrl); + void IncCountFreq(int col, wxString &val) { + MyHashCount::const_iterator it = freqValues[col].find(val); + int cnt=0; + if (it != freqValues[col].end()) + cnt = it->second; + cnt++; + freqValues[col][val] = cnt; + } + + // implementation of base class virtuals to define model + + virtual unsigned int GetColumnCount() const wxOVERRIDE + { + return Col_Max; + } + + unsigned int GetRowCount() const + { + return store->getCountFilter(); + } + + virtual wxString GetColumnType(unsigned int col) const wxOVERRIDE + { + if (col == Col_ToggleIconText) + return wxDataViewCheckIconTextRenderer::GetDefaultType(); + + return "string"; + } + Storage* getStorage() { + return store; + } + virtual void GetValueByRow(wxVariant& variant, + unsigned int row, unsigned int col) const wxOVERRIDE; + virtual bool GetAttrByRow(unsigned int row, unsigned int col, + wxDataViewItemAttr& attr) const wxOVERRIDE; + virtual bool SetValueByRow(const wxVariant& variant, + unsigned int row, unsigned int col) wxOVERRIDE; + MyHashCount freqValues[Col_Max]; + wxBitmap bitmapflt; + unsigned int lastrow; + MyDataViewCtrl* m_view; +private: + ///IntToStringMap m_customColValues; + wxIcon m_icon[MyConst::iconIndex::MAX_COL]; + + Storage* store; + MyConst::colField colmap[Col_Max]; + + +}; + diff --git a/include/log/error_xpm.xpm b/include/log/error_xpm.xpm new file mode 100644 index 0000000..fa133fe --- /dev/null +++ b/include/log/error_xpm.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *error_xpm[] = { +"16 16 5 1 0 0", +" c #000000", +"! c #FF0000", +"# c #333333", +"$ c #FF8080", +"% c None", +"################", +"#%%%%%%%%%%%%%%#", +"#%%!!!!!!%%%%%%#", +"#%%!!!!!!%%%%%%#", +"#%%!!$%%%%%%%%%#", +"#%%!!%%%%%%%%%%#", +"#%%!!%%%%%%%%%%#", +"#%%!!!!!%%%%%%%#", +"#%%!!!!!%%%%%%%#", +"#%%!!$%%%%%%%%%#", +"#%%!!%%%%%%%%%%#", +"#%%!!%%%%%%%%%%#", +"#%%!!!!!!%%%%%%#", +"#%%!!!!!!%%%%%%#", +"#%%%%%%%%%%%%%%#", +"################" +}; diff --git a/include/log/errorl_xpm.xpm b/include/log/errorl_xpm.xpm new file mode 100644 index 0000000..ec40f53 --- /dev/null +++ b/include/log/errorl_xpm.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *errorl_xpm[] = { +"16 16 5 1 0 0", +" c #000000", +"! c #FF0000", +"# c #333333", +"$ c #FF8080", +"% c None", +"################", +"#%%%%%%%%%%%%%%#", +"#%%!!!!!!%%%%%%#", +"#%%!!!!!!%%%%%%#", +"#%%!!$%%%%%%%%%#", +"#%%!!%%%%%%%%%%#", +"#%%!!%%%%%%%%%%#", +"#%%!!!!!%%%%%%%#", +"#%%!!!!!%%%%%%%#", +"#%%!!$%%%%%%%%%#", +"#%%!!%%%%%%%%%%#", +"#%%!!%%%%%%%%%%#", +"#%%!!!!!!%%%%%%#", +"#%%!!!!!!%%%%%%#", +"#%%%%%%%%%%%%%%#", +"################" +}; diff --git a/include/log/fatal_xpm.xpm b/include/log/fatal_xpm.xpm new file mode 100644 index 0000000..51f6ba8 --- /dev/null +++ b/include/log/fatal_xpm.xpm @@ -0,0 +1,26 @@ +/* XPM */ +static char *fatal_xpm[] = { +"16 16 6 1 0 0", +" c #000000", +"! c #FF0000", +"# c #333333", +"$ c #FF8080", +"% c #FFFFFF", +"& c None", +"################", +"#&%%%%%%%%&&&&&#", +"#&%!!!!!!%&&&&&#", +"#&%!!!!!!%&&&&&#", +"#&%!!$%%%%&&&&&#", +"#&%!!%&&&&&&&&&#", +"#&%!!%%%%&&&&&&#", +"#&%!!!!!%&&&&&&#", +"#&%!!!!!%&&&&&&#", +"#&%!!$%%%&&&&&&#", +"#&%!!%&&&&&&&&&#", +"#&%!!%&&&&&&&&&#", +"#&%!!%&&&&&&&&&#", +"#&%!!%&&&&&&&&&#", +"#&%%%%&&&&&&&&&#", +"################" +}; diff --git a/include/log/filter_xpm.xpm b/include/log/filter_xpm.xpm new file mode 100644 index 0000000..9842b18 --- /dev/null +++ b/include/log/filter_xpm.xpm @@ -0,0 +1,34 @@ +/* XPM */ +static char *filter_xpm[] = { +"16 16 14 1 0 0", +" c #000000", +"! c #151515", +"# c #181818", +"$ c #202020", +"% c #242424", +"& c #2C2C2C", +"' c #323232", +"( c #333333", +") c #474747", +"* c #848484", +"+ c #898989", +", c #929292", +"- c #FFFFFF", +". c None", +"................", +"..)&((((((((#!..", +"..* ........ ..", +".. %,...... ...", +"... '..... ....", +"....$+.... .....", +".... ... .....", +"..... ... ......", +"...... .. ......", +"...... .. ......", +"...... .. ......", +"...... .. ......", +"...... .. ......", +"...... .. ......", +"...... ......", +"................" +}; diff --git a/include/log/log_red_xpm.xpm b/include/log/log_red_xpm.xpm new file mode 100644 index 0000000..f3f03a0 --- /dev/null +++ b/include/log/log_red_xpm.xpm @@ -0,0 +1,27 @@ +/* XPM */ +static char *log_red_xpm[] = { +"16 16 7 1 0 0", +" c #000000", +"! c #FF0000", +"# c #2B2B2B", +"$ c #4E4E4E", +"% c #717171", +"& c #FFFFFF", +"' c None", +" ", +" '''''''''''''' ", +" '& %''''''''' ", +" '& #''''''''' ", +" '& #'!!!!!!'' ", +" '& #'!!!!!!'' ", +" '& #'!!!!!!'' ", +" '& #'!!!!!!'' ", +" '& #'!!!!!!'' ", +" '& #'!!!!!!'' ", +" '& #''''''''' ", +" '& #''''''''' ", +" '& $''' ", +" '& $''' ", +" '&&&&&&&&&'''' ", +" " +}; diff --git a/include/log/log_xpm.xpm b/include/log/log_xpm.xpm new file mode 100644 index 0000000..ce761d0 --- /dev/null +++ b/include/log/log_xpm.xpm @@ -0,0 +1,26 @@ +/* XPM */ +static char *log_xpm[] = { +"16 16 6 1 0 0", +" c #000000", +"! c #2B2B2B", +"# c #4E4E4E", +"$ c #717171", +"% c #FFFFFF", +"& c None", +" ", +" &&&&&&&&&&&&&& ", +" &% $&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% !&&&&&&&&& ", +" &% #&&& ", +" &% #&&& ", +" &%%%%%%%%%&&&& ", +" " +}; diff --git a/include/log/null.xpm b/include/log/null.xpm new file mode 100644 index 0000000..bb54efb --- /dev/null +++ b/include/log/null.xpm @@ -0,0 +1,30 @@ +/* XPM */ +static const char *const null_xpm[] = { +"16 16 11 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"@ c #FDE4E4", +"# c #F45C5C", +"$ c #EF0B0B", +"% c #F12727", +"& c #F67878", +"* c #FAAEAE", +"= c #FBC9C9", +"- c #F89393", +"................", +".++++++++++++++.", +".++++++++++++++.", +".++++@#$$%&++++.", +".+++@%$$$$$&+++.", +".+++&$$#*%$$+++.", +".+++=-#+@%$%+++.", +".++++++@%$$*+++.", +".++++++%$%@++++.", +".+++++*$$*+++++.", +".+++++@**@+++++.", +".+++++*$$*+++++.", +".+++++*$$*+++++.", +".+++++@**@+++++.", +".++++++++++++++.", +"................"}; diff --git a/include/log/panic_xpm.xpm b/include/log/panic_xpm.xpm new file mode 100644 index 0000000..8c05cd3 --- /dev/null +++ b/include/log/panic_xpm.xpm @@ -0,0 +1,26 @@ +/* XPM */ +static char *panic_xpm[] = { +"16 16 6 1 0 0", +" c #000000", +"! c #FF0000", +"# c #333333", +"$ c #FF8080", +"% c #FFFFFF", +"& c None", +"################", +"#&%%%%%%%%%&&&&#", +"#&%!!!!!!!!%%&&#", +"#&%!!!!!!!!!%&&#", +"#&%!!$%%%%!!%&&#", +"#&%!!$%&&%!!%&&#", +"#&%!!$%&&%!!%&&#", +"#&%!!$%%%!!!%&&#", +"#&%!!!!!!!!%%&&#", +"#&%!!!!!!!%%&&&#", +"#&%!!$%%%%%&&&&#", +"#&%!!$%&&&&&&&&#", +"#&%!!$%&&&&&&&&#", +"#&%!!$%&&&&&&&&#", +"#&%%%%&&&&&&&&&#", +"################" +}; diff --git a/include/log/user_xpm.xpm b/include/log/user_xpm.xpm new file mode 100644 index 0000000..f7e8c09 --- /dev/null +++ b/include/log/user_xpm.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static char *user_xpm[] = { +"16 16 3 1 0 0", +" c #000000", +"! c #3F28D9", +"# c None", +" ", +" ############## ", +" ############## ", +" ##!!###!!##### ", +" ##!!###!!##### ", +" ##!!###!!##### ", +" ##!!###!!##### ", +" ##!!###!!##### ", +" ##!!###!!##### ", +" ##!!###!!##### ", +" ##!!###!!##### ", +" ##!!###!!##### ", +" ###!!!!!###### ", +" ############## ", +" ############## ", +" " +}; diff --git a/include/log/war_xpm.xpm b/include/log/war_xpm.xpm new file mode 100644 index 0000000..5956266 --- /dev/null +++ b/include/log/war_xpm.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static char *war_xpm[] = { +"16 16 3 1 0 0", +" c #000000", +"! c #C0C0C0", +"# c None", +" ", +" ############## ", +" ############## ", +" ############## ", +" ### ### ! ", +" !## ### ! ", +" # !# ! !#! !! ", +" # !! ! # !# ", +" # ! ! # !# ", +" # ! !# # !# ", +" ## ! !!# ! !!# ", +" ## !## !## ", +" ## !## !## ", +" ## !## !## ", +" ############## ", +" " +}; diff --git a/include/log/wx_small.xpm b/include/log/wx_small.xpm new file mode 100644 index 0000000..115a048 --- /dev/null +++ b/include/log/wx_small.xpm @@ -0,0 +1,28 @@ +/* XPM */ +static const char *wx_small_xpm[] = { +/* columns rows colors chars-per-pixel */ +"16 16 6 1", +". c Black", +"o c #FFFFFF", +"X c #000080", +"O c #FFFF00", +" c None", +"+ c #FF0000", +/* pixels */ +" ", +" ", +" ", +" ....... ", +" .XXXXX. ", +" .oXXXX. ", +" .oXXX.......", +".....oXXX.OOOOO.", +".+++.XXXX.oOOOO.", +".o++......oOOOO.", +".o++++. .oOOOO.", +".o++++. .OOOOO.", +".+++++. .......", +"....... ", +" ", +" " +}; diff --git a/include/schema/pgServer.h b/include/schema/pgServer.h index edf5e96..277decf 100644 --- a/include/schema/pgServer.h +++ b/include/schema/pgServer.h @@ -666,12 +666,19 @@ public: }; +class disconnectServerFactoryAll : public contextActionFactory +{ +public: + disconnectServerFactoryAll(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar); + wxWindow *StartDialog(frmMain *form, pgObject *obj); + bool CheckEnable(pgObject *obj); +}; class disconnectServerFactory : public contextActionFactory { public: - disconnectServerFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar); - wxWindow *StartDialog(frmMain *form, pgObject *obj); - bool CheckEnable(pgObject *obj); + disconnectServerFactory(menuFactoryList* list, wxMenu* mnu, ctlMenuToolbar* toolbar); + wxWindow* StartDialog(frmMain* form, pgObject* obj); + bool CheckEnable(pgObject* obj); }; class reloadconfServiceFactory : public contextActionFactory diff --git a/include/utils/pgfeatures.h b/include/utils/pgfeatures.h index d3b2381..87eb66c 100644 --- a/include/utils/pgfeatures.h +++ b/include/utils/pgfeatures.h @@ -26,6 +26,7 @@ enum FEATURE_PGSTATINDEX, FEATURE_PGCHECKINDEX, FEATURE_FUNCTION_DEFAULTS, + FEATURE_CSVLOG, FEATURE_LAST }; diff --git a/pgAdmin3.cpp b/pgAdmin3.cpp index 0bac68c..c36edf6 100644 --- a/pgAdmin3.cpp +++ b/pgAdmin3.cpp @@ -54,6 +54,7 @@ #include "frm/frmConfig.h" #include "frm/frmQuery.h" #include "frm/frmStatus.h" +#include "frm/frmLog.h" #ifdef DATABASEDESIGNER #include "frm/frmDatabaseDesigner.h" #endif @@ -281,6 +282,7 @@ bool pgAdmin3::OnInit() {wxCMD_LINE_SWITCH, "h", "help", _("show this help message, and quit"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP }, {wxCMD_LINE_OPTION, "s", "server", _("auto-connect to specified server"), wxCMD_LINE_VAL_STRING}, {wxCMD_LINE_SWITCH, "S", "serverstatus", _("open server status window"), wxCMD_LINE_VAL_NONE}, + {wxCMD_LINE_SWITCH, "L", "log window", _("open server log window"), wxCMD_LINE_VAL_NONE}, {wxCMD_LINE_OPTION, "Sc", "serverstatusconnect", _("connect server status window to database"), wxCMD_LINE_VAL_STRING}, {wxCMD_LINE_SWITCH, "q", "query", _("open query tool"), wxCMD_LINE_VAL_NONE}, {wxCMD_LINE_OPTION, "qc", "queryconnect", _("connect query tool to database"), wxCMD_LINE_VAL_STRING}, @@ -575,10 +577,11 @@ bool pgAdmin3::OnInit() } if (!conn) return false; - - wxString txt = _("Server Status - ") + conn->GetName(); - frmStatus *fq = new frmStatus(NULL, txt, conn); - fq->Go(); + + wxString txt = _("Server Status - ") + conn->GetName(); + frmStatus* fq = new frmStatus(NULL, txt, conn); + fq->Go(); + } #ifdef DATABASEDESIGNER @@ -793,6 +796,14 @@ bool pgAdmin3::OnInit() if (cmdParser.Found(wxT("s"), &str)) { pgServer *srv = winMain->ConnectToServer(str, !cmdParser.Found(wxT("q"))); + if (srv && cmdParser.Found(wxT("L"))) { + //PgConn* newcon = srv->GetConnection()->Duplicate("Log conn"); + wxString txt = _("Log - ") + srv->GetConnection()->GetName(); + winMain->Logfrm = new frmLog(winMain, txt, srv); + winMain->Logfrm->Go(); + + } + if (srv && cmdParser.Found(wxT("q"))) { pgConn *conn; diff --git a/pgAdmin3.vcxproj b/pgAdmin3.vcxproj index f2e75bd..f53b7fe 100644 --- a/pgAdmin3.vcxproj +++ b/pgAdmin3.vcxproj @@ -905,6 +905,7 @@ + @@ -1012,6 +1013,9 @@ + + + @@ -1353,6 +1357,16 @@ + + + + + + + + + + @@ -1528,6 +1542,10 @@ + + + + diff --git a/pgAdmin3.vcxproj.filters b/pgAdmin3.vcxproj.filters index 58372cf..ce053ba 100644 --- a/pgAdmin3.vcxproj.filters +++ b/pgAdmin3.vcxproj.filters @@ -251,6 +251,9 @@ {bc8bb60a-46b2-4f26-b86f-f711fcc2a385} + + {e2f5428c-112b-47e2-8394-cf4a5c1a8d8a} + @@ -1635,6 +1638,18 @@ dlg + + frm + + + utils + + + utils + + + utils + @@ -2154,6 +2169,36 @@ ui + + include\log + + + include\log + + + include\log + + + include\log + + + include\log + + + include\log + + + include\log + + + include\log + + + include\log + + + include\log + @@ -3491,6 +3536,18 @@ include\dlg + + include\frm + + + include\log + + + include\log + + + include\log + diff --git a/schema/pgServer.cpp b/schema/pgServer.cpp index a2f7492..04b3ac5 100644 --- a/schema/pgServer.cpp +++ b/schema/pgServer.cpp @@ -2170,6 +2170,67 @@ bool disconnectServerFactory::CheckEnable(pgObject *obj) return false; } +disconnectServerFactoryAll::disconnectServerFactoryAll(menuFactoryList* list, wxMenu* mnu, ctlMenuToolbar* toolbar) : contextActionFactory(list) +{ + mnu->Append(id, _("Disconnec&t all servers"), _("Disconnect from all servers.")); +} + + +wxWindow* disconnectServerFactoryAll::StartDialog(frmMain* form, pgObject* obj) +{ + + wxTreeItemIdValue foldercookie, servercookie; + wxTreeItemId folderitem, serveritem; + pgObject* object; + pgServer* server; + folderitem = form->GetBrowser()->GetFirstChild(form->GetBrowser()->GetRootItem(), foldercookie); + while (folderitem) + { + if (form->GetBrowser()->ItemHasChildren(folderitem)) + { + serveritem = form->GetBrowser()->GetFirstChild(folderitem, servercookie); + while (serveritem) + { + object = form->GetBrowser()->GetObject(serveritem); + if (object && object->IsCreatedBy(serverFactory)) + { + if (CheckEnable(object)) { + if (object->CheckOpenDialogs(form->GetBrowser(), serveritem)) + { + wxString msg = _("There are properties dialogues open for one or more objects belonging to a database which will be disconnected. Please close the properties dialogues and try again."); + wxMessageBox(msg, _("Cannot disconnect database"), wxICON_WARNING | wxOK); + } + else + { + server = (pgServer*)object; + server->Disconnect(form); + server->UpdateIcon(form->GetBrowser()); + form->GetBrowser()->DeleteChildren(object->GetId()); + form->execSelChange(object->GetId(), true); + } + } + } + serveritem = form->GetBrowser()->GetNextChild(folderitem, servercookie); + } + } + folderitem = form->GetBrowser()->GetNextChild(form->GetBrowser()->GetRootItem(), foldercookie); + } + + + return 0; +} + + +bool disconnectServerFactoryAll::CheckEnable(pgObject* obj) +{ + if (obj && obj->IsCreatedBy(serverFactory)) + return ((pgServer*)obj)->GetConnected(); + + return false; +} + + + reloadconfServiceFactory::reloadconfServiceFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list) { mnu->Append(id, _("Reload configuration"), _("Reload configuration")); diff --git a/utils/log/MyDataViewCtrl.cpp b/utils/log/MyDataViewCtrl.cpp new file mode 100644 index 0000000..1abc194 --- /dev/null +++ b/utils/log/MyDataViewCtrl.cpp @@ -0,0 +1,470 @@ +#include "pgAdmin3.h" + +#include "log/MyDataViewCtrl.h" +#include "log/StorageModel.h" +#include "wx/headerctrl.h" +#include "wx/itemattr.h" +#include "wx/dataview.h" +//#include +#include + + +BEGIN_EVENT_TABLE(MyDataViewCtrl, wxDataViewCtrl) +EVT_MOTION(MyDataViewCtrl::OnMouseMove) + +END_EVENT_TABLE() + +#ifdef MYTEST +#include +void MyDataViewCtrl::OnTimer(wxTimerEvent& event) { + // wxLogMessage("OnTimer called."); + if (linenumber == -1) { + wxTextFile tfile; + wxString str; + tfile.Open("logAdd.csv"); + + // read the first line + str = tfile.GetFirstLine() + "\n"; + + // read all lines one by one + // until the end of the file + while (!tfile.Eof()) + { + str += tfile.GetNextLine() + "\n"; + } + tfile.Close(); + CSVLineTokenizer tk(str); + wxString line; + while (tk.HasMoreLines()) + { + line.Clear(); + + bool partial; + line = tk.GetNextLine(partial); + if (partial) + { + break; + } + //Prepend(line); + logadd.Add(line); + + } + linenumber = 0; + } + if (logadd.GetCount() > linenumber) { + StorageModel* m = dynamic_cast(GetModel()); + //m->Prepend(logadd[linenumber++]); + AddRow(logadd[linenumber++]); + wxString l = wxString::Format("rows %d", m->GetCount()); + st->SetLabelText(l); + if (m->getGroupFilter()) Refresh(); + } +} +#endif +void MyDataViewCtrl::setSettingString(wxString setstr) { + wxString s; + wxStringTokenizer tokenizer(setstr, ";"); + + for (int i = 0; i < GetColumnCount(); i++) { + if (!tokenizer.HasMoreTokens()) break; + wxString token = tokenizer.GetNextToken(); + wxDataViewColumn* vc = GetColumn(i); + int w; + w = wxAtoi(token); + vc->SetWidth(w); + } + return; +} + +wxString MyDataViewCtrl::getSettingString() { + wxString s; + for (int i = 0; i < GetColumnCount(); i++) { + wxDataViewColumn* vc = GetColumn(i); + int width = vc->GetWidth(); + if (!s.IsEmpty()) s = s + ";"; + s = s + wxString::Format("%d", width); + } + //s = s + wxString::Format(";%d", modeGroup); + return s; +} +void MyDataViewCtrl::setGroupMode(bool mode) { + StorageModel* m = dynamic_cast(GetModel()); + m->setGroupFilter(mode); + wxItemAttr attr; + if (m->getGroupFilter()) + { + //attr.SetTextColour(*wxRED); + //attr.GetFont().Underlined(); + attr.SetFont(attr.GetFont().Underlined()); + //attr.GetFont(). + //wxFontStyle s=attr.GetFont().GetStyle(); + //attr.SetFont(attr.GetFont().Strikethrough()); + } + //else: leave it as default to disable custom header attributes + wxString l = wxString::Format("rows %d", m->GetRowCount()); + st->SetLabelText(l); + + if (!SetHeaderAttr(attr)) + wxLogMessage("Sorry, header attributes not supported on this platform"); + +} +void MyDataViewCtrl::ViewGroup(bool view) { + StorageModel* m = dynamic_cast(GetModel()); + Storage* sta = m->getStorage(); + if (view) { + selectRowGroup = GetSelection(); + if (!selectRowGroup.IsOk()) return; + int rowselectGroup = m->GetRow(selectRowGroup); + sta->setDetailGroupRow(rowselectGroup); + + } + else sta->setDetailGroupRow(-1); + m->ApplyFilter(); + if (!view) { + SetCurrentItem(selectRowGroup); + EnsureVisible(selectRowGroup); + } + wxString l = wxString::Format("rows %d", m->GetRowCount()); + st->SetLabelText(l); + + +} +void MyDataViewCtrl::AddRow(wxString csvtext) { + StorageModel* m = dynamic_cast(GetModel()); + wxDataViewItem select; + if (HasSelection()) select = GetSelection(); + + + if (m->Prepend(csvtext)) { + if (!select.IsOk()) return; + int rowselect = m->GetRow(select); + // add visible row + int rowlast = m->GetRowCount() - 1; + if ((rowlast - 1) == rowselect) { + //m->GetItem(); + select = m->GetItem(rowlast); + SetCurrentItem(select); + EnsureVisible(select); + ScrollPages(1); + } + + } + + +} +void MyDataViewCtrl::OnEVT_DATAVIEW_SELECTION_CHANGED(wxDataViewEvent& event) { + StorageModel* m = dynamic_cast(GetModel()); + int r = m->GetRow(event.GetItem()); + if (m->getGroupFilter() && r != -1) { + Storage* sta = m->getStorage(); + sta->ClearCount(r); + int a=sta->GetTotalCountGroup(r); + + wxString l = wxString::Format("Total rows in group %d", a); + st->SetLabelText(l); + + //EnsureVisible(event.GetItem()); + } + + //wxLogMessage("wxEVT_DATAVIEW_SELECTION_CHANGED, First selected Item row: %d", r); + +} +void MyDataViewCtrl::ClearAllFilter() { + int colt = 0; + int col = 0; + bool all = false; + bool clear = false; + wxArrayInt fCol; + StorageModel* m = dynamic_cast(GetModel()); + for (int j = 0; j < GetColumnCount(); j++) { + col = j; + clear = false; + colt = 0; + while (colt != -1) { + colt = m->testFilter(col, colt); + if (colt >= 0) { + m->DropColFilter(colt); + clear = true; + all = true; + colt++; + } + } + if (clear) { + // m->ApplyFilter(); + fCol.Add(col); + // wxDataViewColumn* vc = GetColumn(col); + // vc->SetBitmap(wxNullBitmap); + } + } + if (all) { + m->ApplyFilter(); + for (auto i : fCol) { + wxDataViewColumn* vc = GetColumn(i); + vc->SetBitmap(wxNullBitmap); + } + } + wxString l = wxString::Format("rows %d", m->GetRowCount()); + st->SetLabelText(l); +} +void MyDataViewCtrl::AddFilterIgnore() { + int colt = 0; + int col = 0; + bool all = false; + bool clear = false; + wxArrayInt fCol; + StorageModel* m = dynamic_cast(GetModel()); + Storage* st = m->getStorage(); + wxString text; + for (int j = 0; j < GetColumnCount(); j++) { + col = j; + clear = false; + colt = 0; + while (colt != -1) { + colt = m->testFilter(col, colt); + if (colt >= 0) { + //m->DropColFilter(colt); + wxString expr=st->GetStringFilterExpr(colt,true); + st->addLineFilterStr(expr); + text = text + expr + "\r\n"; + all = true; + colt++; + } + } + } + + if (all) { + st->addLineFilterStr(""); + wxMessageBox("Create load filter\r\n\r\n"+text, _("Warning"), wxICON_INFORMATION | wxOK); + + } + else { + wxMessageBox("Filter empty.", _("Warning"), wxICON_INFORMATION | wxOK); + } + +} +void MyDataViewCtrl::OnEVT_DATAVIEW_CONTEXT_MENU(wxCommandEvent& event) { + int id = event.GetId(); + wxMenu* mi = static_cast(event.GetEventObject()); + wxString label = mi->GetLabelText(id); + StorageModel* m = dynamic_cast(GetModel()); + + int col = (int)mi->GetClientData(); + if (id > 100) { + int colt = 0; + bool clear = false; + //int pos = 0; + bool all = (label == "Clear All"); + Storage* stor = m->getStorage(); + wxString expr; + while (colt != -1) { + colt = m->testFilter(col, colt); + if (colt >= 0) { + expr = stor->GetStringFilterExpr(colt); + if (expr == label || all) { + m->DropColFilter(colt); + clear = true; + } + colt++; + } + } + if (clear) { + m->ApplyFilter(); + if (m->testFilter(col, 0) == -1) { + wxDataViewColumn* vc = GetColumn(col); + vc->SetBitmap(wxNullBitmap); + } + } + } + else { + m->setFilter(col, label.AfterFirst(' '), 0, this); + //wxDataViewColumn* vc=GetColumn(col); + //vc->SetBitmap(m->bitmapflt); + + } + wxString l = wxString::Format("rows %d", m->GetRowCount()); + st->SetLabelText(l); + + if (col == StorageModel::Col_User) { + + + //m->setFilter(col, label.AfterFirst(' ')); + // wxMessageBox("You have selected Item 1", "Your selection", wxOK | wxICON_INFORMATION); + } + if (event.GetId() == 2) { + // wxMessageBox("You have selected Item 2", "Your selection", wxOK | wxICON_INFORMATION); + } + +} +// +void MyDataViewCtrl::OnContextMenu(wxDataViewEvent& event) { + //wxString title = m_music_model->GetTitle(event.GetItem()); + //wxLogMessage("wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, Item: %s", title); + StorageModel* m = dynamic_cast(GetModel()); + int row = m->GetRow(event.GetItem()); + int ncol; + wxVariant vr; + if (row >= 0) + { + + //int ncol = GetColumn(event.GetColumn())->GetModelColumn(); + ncol = event.GetColumn(); + m->GetValueByRow(vr, row, ncol); + if (ncol == 0) { + wxDataViewIconText ic; + ic << vr; + wxString str = ic.GetText(); + vr = str; + } + } + int pos = GetColumnPosition(event.GetDataViewColumn()); + + wxString str = wxString::Format("col: %d row: %d val: %s", pos, row, vr.GetString()); + int flags = 0; + if (modctrl) flags = FL_REVERSE; + m->setFilter(ncol, vr.GetString(), flags, this); + //wxDataViewColumn* vc = GetColumn(event.GetColumn()); + //vc->SetBitmap(m->bitmapflt); + + //wxMessageBox(str, "Your selection", wxOK | wxICON_INFORMATION); + + +} +auto cmp = [](std::pair const& a, std::pair const& b) +{ + return a.second != b.second ? a.second < b.second : a.first < b.first; +}; +void MyDataViewCtrl::OnEVT_DATAVIEW_COLUMN_HEADER_CLICK(wxDataViewEvent& event) { + StorageModel* m = dynamic_cast(GetModel()); + int col = event.GetColumn(); + if (event.GetColumn() == StorageModel::Col_User) + { + } + const int rowChanged = m->GetRow(event.GetItem()); + wxMenu menu; + //menu.SetTitle(event.GetDataViewColumn()->GetTitle()); + int i = 1; + int colt = m->testFilter(col, 0); + if (colt >= 0) { + wxMenu* menus = new wxMenu(); + menus->SetClientData((void*)col); + Storage* stor = m->getStorage(); + menus->Append(101 + i++, "Clear All"); + wxString expr; + while (colt != -1) { + expr = stor->GetStringFilterExpr(colt); + menus->Append(101 + i++, expr); + colt = m->testFilter(col, colt + 1); + } + menu.AppendSubMenu(menus, "Clear filters"); + } + std::set> items; + std::pair v; + int cc = 0; + MyHashCount::iterator it; + for (it = m->freqValues[col].begin(); it != m->freqValues[col].end(); ++it) + { + v = std::make_pair(it->second, it->first.ToStdString()); + items.emplace(v); + //ma.emplace(it->first.ToStdString(), it->second); + cc++; + } + //std::sort(items.begin(), items.end(),cmp); + cc = cc - 20; + int cc0 = 0; + for (auto const& vk : items) { + cc0++; + if (cc0 < cc) continue; + wxString key = wxString(vk.second); + int value = vk.first; + wxString str = wxString::Format("%d %s", value, key); + menu.Append(i++, str); + + } + + //for (it = m->freqValues[col].begin(); it != m->freqValues[col].end(); ++it) + //{ + // wxString key = it->first; + // int value = it->second; + // wxString str = wxString::Format("%d %s",value,key); + // menu.Append(i++, str); + //} + + menu.SetClientData((void*)col); + this->PopupMenu(&menu); + event.Skip(false); + return; + + event.Skip(true); +} +void MyDataViewCtrl::OnKEY_DOWN(wxKeyEvent& event) { + if ((event.GetModifiers() & wxMOD_CONTROL) == wxMOD_CONTROL) { + modctrl = true; + } + else modctrl = false; + event.Skip(true); + +} +void MyDataViewCtrl::OnMouseMove(wxMouseEvent& event) { + + //event.Skip(true); + if (event.Dragging()) + return; + + wxClientDC dc(GetMainWindow()); + PrepareDC(dc); + + //wxPoint logPos(event.GetLogicalPosition(dc)); + //wxPoint mc= GetMainWindow()->ScreenToClient(event.GetPosition()); + wxPoint mc = event.GetPosition(); + wxString position; + //position = wxString::Format("x=%d y=%d", logPos.x, logPos.y); + //wxLogMessage("Mouse pos %s", position); + wxHeaderCtrl* const header = GenericGetHeader(); + int dy = 0; + wxSize sz; + if (header) { + sz = header->GetSize(); + //header->Refresh(); + dy = sz.GetHeight(); + } + mc.y += dy; + wxDataViewItem item; + wxDataViewColumn* column; + + HitTest(mc, item, column); + if (item != NULL && column != NULL) + { + StorageModel* m; + m = dynamic_cast(GetModel()); + + int row = m->GetRow(item); + + if (row >= 0) + { + wxVariant vr; + int ncol = column->GetModelColumn(); + if ((lastcol != ncol) || (lastrow != row)) { + m->GetValueByRow(vr, row, ncol); + wxSize szext = dc.GetTextExtent(vr.GetString()); + int w = column->GetWidth(); + if (szext.GetWidth() > w) + { + GetMainWindow()->SetToolTip(vr.GetString()); + } + else + GetMainWindow()->UnsetToolTip(); + lastrow = row; + lastcol = ncol; + } + + } + else + { + GetMainWindow()->UnsetToolTip(); + } + } + else + { + GetMainWindow()->UnsetToolTip(); + } +} diff --git a/utils/log/Storage.cpp b/utils/log/Storage.cpp new file mode 100644 index 0000000..bcac9e4 --- /dev/null +++ b/utils/log/Storage.cpp @@ -0,0 +1,583 @@ +#include "pgAdmin3.h" +#include "log/Storage.h" +#include "utils/utffile.h" +#include +#include + +int Storage::getCountStore() { + return storage.size(); +} +int Storage::getCountFilter() { + int cnt = frows.size(); + if (cnt == 0 && !IsFilter()) cnt = getCountStore(); + return cnt; +} +LineFilter Storage::getLineFilter(wxString strflt) { + LineFilter lf; + lf.col = -1; + if (strflt.IsEmpty()) { + return lf; + } + int col = wxAtoi(strflt.BeforeFirst(':')); + lf.col = col; + wxString l = strflt.AfterFirst(':'); + wxString val = l.AfterFirst(' '); + int flags = 0; + int i = 0; + if (l[i] == '!') { flags = FL_REVERSE; i++; } + if (l[i] == '~') flags = flags | FL_CONTAINS; + lf.flags = flags; + lf.val = val; + return lf; +} +wxString Storage::_strwhere(int flags) { + wxString expr = "= "; + if ((flags & FL_CONTAINS) == FL_CONTAINS) expr = "~ "; + if ((flags & FL_REVERSE) == FL_REVERSE) expr = "!" + expr; + return expr; +} +wxString Storage::LineFilterToStr(LineFilter& lf) { + if (lf.col == -1) { + return ""; + } + wxString expr = _strwhere(lf.flags)+lf.val; + expr = wxString::Format("%d:%s", lf.col, expr); + return expr; +} + +void Storage::addLineFilterStr(wxString strflt) { + LineFilter lf = getLineFilter(strflt); + filterload.push_back(lf); +} +Storage::Storage() { + + bgErr[MyConst::iconIndex::log] = wxNullColour; + bgErr[MyConst::iconIndex::war] = wxNullColour; + bgErr[MyConst::iconIndex::user] = wxColor(250, 196, 186); + bgErr[MyConst::iconIndex::error] = wxColor(220, 150, 150); + bgErr[MyConst::iconIndex::fatal] = wxColor(209, 100, 100); + bgErr[MyConst::iconIndex::panic] = wxColor(246, 10, 10); + + // load filter + wxString tempDir = wxStandardPaths::Get().GetUserConfigDir() + wxT("\\postgresql\\"); + + wxString f = tempDir + "filter_load.txt"; + if (wxFileExists(f)) { + wxString str; + wxUtfFile file(f, wxFile::read, wxFONTENCODING_UTF8); + if (file.IsOpened()) { + file.Read(str); + file.Close(); + wxStringTokenizer tk(str, "\n", wxTOKEN_RET_EMPTY_ALL); + + bool end = false; + LineFilter lf; + while (tk.HasMoreTokens()) + { + wxString l = tk.GetNextToken(); + if (end && l.IsEmpty()) continue; + addLineFilterStr(l); + end = l.IsEmpty(); + } + if (!end) addLineFilterStr(""); + + } + + } +} +void Storage::saveFilters() { + if (filterload.size() == 0) return; + wxString tempDir = wxStandardPaths::Get().GetUserConfigDir() + wxT("\\postgresql\\"); + wxString f = tempDir + "filter_load.txt"; + wxUtfFile file(f, wxFile::write, wxFONTENCODING_UTF8); + if (file.IsOpened()) + { + wxString text; + for (auto lf:filterload) { + wxString s = LineFilterToStr(lf); + + if (!s.IsEmpty()) text.Append(s); + text.Append("\n"); + } + if ((file.Write(text) == 0)) + wxMessageBox(_("Query text incomplete.\nQuery contained characters that could not be converted to the local charset.\nPlease correct the data or try using UTF8 instead.")); + file.Close(); + } + +} +void Storage::getLineToCache(int row, bool filter) { + int r = row; + if (filter && IsFilter()) row = frows[row]; + m_cacheLine = storage.at(row); + m_cacheIndex = r; +} +int Storage::GetSeverityIndex(int rowvisual) { + if (rowvisual != m_cacheIndex) getLineToCache(rowvisual, true); + return m_cacheLine.icon; +} +wxColor& Storage::GetBgColorLine(int rowvisual) { + if (rowvisual != m_cacheIndex) getLineToCache(rowvisual, true); + int i = m_cacheLine.icon; + return bgErr[i]; +} +void Storage::DropColFilter(int index) { + fCol.RemoveAt(index); + fVal.RemoveAt(index); + fFlags.RemoveAt(index); + // storage + frows.clear(); +} +wxString Storage::GetStringFilterExpr(int positionArrayFilter,bool addNumCol) { + wxString expr; + expr = _strwhere(fFlags[positionArrayFilter]); + expr = expr + fVal[positionArrayFilter]; + if (addNumCol) { + expr = wxString::Format("%d:%s", fCol[positionArrayFilter], expr); + return expr; + } + if (expr.Len() > 50) expr = expr.substr(0, 50); + return expr; +} +int Storage::testFilter(MyConst::colField col, int position) { + int k = 0; + for (auto i : fCol) { + if (i == col && (position <= k)) return k; + k++; + } + return -1; +} +bool Storage::CompareFilterLine(int row, bool filter) { + int c = 0; + MyConst::colField f; + wxString valFlt; + wxString valField; + bool reverse = false; + bool rez = false; + int flags = 0; + for (auto i : fCol) { + flags = fFlags[c]; + reverse = (flags & FL_REVERSE) == FL_REVERSE; + valFlt = fVal[c++]; + f = (MyConst::colField) i; + valField = GetFieldStorage(row, f, filter); + if ((flags & FL_CONTAINS) == 0) rez = valField != valFlt; + else + rez = !valField.Contains(valFlt); + if (reverse) rez = !rez; + if (rez) { + return false; + } + } + // + if (IsGroupFilter()) + { + // + if (filter) return true; + + // + if (faddgroup && detailGroup != -1) return false; + // + if (detailGroup != -1 && detailGroup == prevRow && !filter) { + // + // + detailGroup = row; + return true; + } + + if (!faddgroup) return false; + + } + return true; +} +// +void Storage::setDetailGroupRow(int rowGroup) { + if (IsGroupFilter()) { + // + if (rowGroup != -1) detailGroup = frows[rowGroup]; else detailGroup = -1; + } + else detailGroup = -1; +} +// strage ( ) +bool Storage::ApplyFilter(int row) { + //if (!IsFilter()) return true; + if (row != -1) { + // verify row + if (!IsFilter()) return true; + // filter enable + if (CompareFilterLine(row, false)) { + frows.push_back(row); + faddgroup = false; + return true; + } + if (detailGroup != -1) return false; + if (IsGroupFilter()) { + // + for (int i = 0; i < frows.size(); i++) { + // + if (frows[i] != prevRow) continue; + frows[i] = row; + return false; + } + + } + // no visible + return false; + } + if (!IsFilter()) { frows.clear(); return true; } + + bool f = false; + faddgroup = true; + if (IsGroupFilter()) { + // GroupFilter hashKeyToRow + frows.clear(); + MyHashToRow::iterator it; + for (it = hashKeyToRow.begin(); it != hashKeyToRow.end(); ++it) + { + int keyhash = it->first, lastrow = it->second; + // do something useful with key and value + if (detailGroup != -1) { + if (detailGroup == lastrow) + { + int l = lastrow; + while (l != -1) { + frows.push_back(l); + if (l != m_cacheIndex) getLineToCache(l, false); + l = m_cacheLine.prevRowGroup; + } + break; + } + } + else + frows.push_back(lastrow); + //frows.push_back(keyhash); + } + sort(frows.begin(), frows.end()); + + } + if (frows.size() > 0) { + // + std::deque tmp; + for (int i = 0; i < frows.size(); i++) { + if (CompareFilterLine(i, true)) { + + tmp.push_back(frows[i]); + f = true; + }; + + } + if (f) frows = tmp; + else frows.clear(); + } + else + { + // + for (int i = 0; i < storage.size(); i++) { + if (CompareFilterLine(i, false)) { + frows.push_back(i); + f = true; + }; + + } + } + faddgroup = false; + return f; +} + +int Storage::SetFilter(int col, wxString& val, int flags) { + fCol.Add(col); + fVal.Add(val); + fFlags.Add(flags); + ApplyFilter(); + return frows.size(); +} +wxString Storage::GetField(int row, MyConst::colField col) { + return GetFieldStorage(row, col, IsFilter()); +} +wxString Storage::get_field(Line& l, MyConst::colField col) { + wxString val; + switch (col) + { + case MyConst::colField::logtime: + return l.text.substr(l.logtime.s, l.logtime.l); + break; + case MyConst::colField::loguser: + return l.text.substr(l.loguser.s, l.loguser.l); + break; + case MyConst::colField::logdb: + return l.text.substr(l.logdb.s, l.logdb.l); + break; + case MyConst::colField::logpid: + return l.text.substr(l.logpid.s, l.logpid.l); + break; + case MyConst::colField::loghost: + return l.text.substr(l.loghost.s, l.loghost.l); + break; + case MyConst::colField::logtag: + return l.text.substr(l.logtag.s, l.logtag.l); + break; + case MyConst::colField::logSessiontime: + return l.text.substr(l.logSessiontime.s, l.logSessiontime.l); + break; + case MyConst::colField::logSeverity: + return l.text.substr(l.logSeverity.s, l.logSeverity.l); + break; + case MyConst::colField::logSqlstate: + return l.text.substr(l.logSqlstate.s, l.logSqlstate.l); + break; + case MyConst::colField::logMessage: + return l.text.substr(l.logMessage.s, l.logMessage.l); + break; + case MyConst::colField::logDetail: + return l.text.substr(l.logDetail.s, l.logDetail.l); + break; + case MyConst::colField::logHint: + return l.text.substr(l.logHint.s, l.logHint.l); + break; + case MyConst::colField::logappname: + val = l.text.substr(l.logappname.s, l.logappname.l); + if (val.IsEmpty()) val = l.text.substr(l.logbtype.s, l.logbtype.l); + return val; + break; + case MyConst::colField::logbtype: + return l.text.substr(l.logbtype.s, l.logbtype.l); + break; + default: + break; + } + return "bad"; + +} +wxString Storage::GetFieldStorage(int row, MyConst::colField col, bool filter) { + if (row != m_cacheIndex) getLineToCache(row, filter); + return get_field(m_cacheLine, col); +} +Line Storage::getLineParse(const wxString& str, bool csv) { + Line st; + if (csv) { + CSVTokenizer tk(str); + // Get the fields from the CSV log. + + wxString t; + wxString logTime = tk.GetNextToken(); + //fields.Add(logTime); + st.logtime = { 0,static_cast(logTime.Len()) }; + wxString logUser = tk.GetNextToken(); + t = logTime; + st.loguser = { static_cast(t.Len()),static_cast(logUser.Len()) }; + t += logUser; + wxString logDatabase = tk.GetNextToken(); + if (logDatabase.IsEmpty()) logDatabase = GetHost(); + st.logdb = { static_cast(t.Len()),static_cast(logDatabase.Len()) }; + + t += logDatabase; + wxString logPid = tk.GetNextToken(); + st.logpid = { static_cast(t.Len()),static_cast(logPid.Len()) }; + t += logPid; + wxString logSession; + wxString logCmdcount; + wxString logSegment; + wxString logHost = tk.GetNextToken(); // Postgres puts port with Hostname + // delete port + logHost = logHost.BeforeFirst(':'); + st.loghost = { static_cast(t.Len()),static_cast(logHost.Len()) }; + t += logHost; + + logSession = tk.GetNextToken(); + + wxString logLineNumber = tk.GetNextToken(); + wxString logTag = tk.GetNextToken(); + st.logtag = { static_cast(t.Len()),static_cast(logTag.Len()) }; + t += logTag; + wxString logSessiontime = tk.GetNextToken(); + st.loghost = { static_cast(t.Len()),static_cast(logHost.Len()) }; + t += logHost; + + wxString logVXid = tk.GetNextToken(); + wxString logTransaction = tk.GetNextToken(); + wxString logSeverity = tk.GetNextToken(); + //fields.Add(logSeverity); + st.logSeverity = { static_cast(t.Len()),static_cast(logSeverity.Len()) }; + t += logSeverity; + + wxString logState = tk.GetNextToken(); + //fields.Add(logState); + st.logSqlstate = { static_cast(t.Len()),static_cast(logState.Len()) }; + t += logState; + + wxString logMessage = tk.GetNextToken(); + //fields.Add(logMessage); + wxString logDetail = tk.GetNextToken(); + //fields.Add(logDetail); + st.logDetail = { static_cast(t.Len()),static_cast(logDetail.Len()) }; + t += logDetail; + wxString logHint = tk.GetNextToken(); + //fields.Add(logHint); + wxString logQuery = tk.GetNextToken(); + wxString logQuerypos = tk.GetNextToken(); + wxString logContext = tk.GetNextToken(); + wxString logDebug = tk.GetNextToken(); + wxString logCursorpos = tk.GetNextToken(); + if (!logDebug.IsEmpty() && logHint.IsEmpty()) + logHint = logDebug; + st.logHint = { static_cast(t.Len()),static_cast(logHint.Len()) }; + t += logHint; + st.logMessage = { static_cast(t.Len()),static_cast(logMessage.Len()) }; + t += logMessage; + + //location + logCursorpos = tk.GetNextToken(); + + wxString logApp = tk.GetNextToken(); + //fields.Add(logApp); + st.logappname = { static_cast(t.Len()),static_cast(logApp.Len()) }; + t += logApp; + wxString logType = tk.GetNextToken(); + st.logbtype = { static_cast(t.Len()),static_cast(logType.Len()) }; + t += logType; + //fields.Add(logType); + //st.logType = { t.Len(),logType.Len() }; + //t += logType; + st.type = MyConst::ltype::SIMPLE_TEXT; + int i = MyConst::iconIndex::log; + if (logSeverity == "WARNING") i = MyConst::iconIndex::war; + if (logSeverity == "ERROR") i = MyConst::iconIndex::error; + if (logSeverity == "FATAL") i = MyConst::iconIndex::fatal; + if (logSeverity == "PANIC") i = MyConst::iconIndex::panic; + st.icon = i; + st.visible = 1; + st.text = t; + + } + return st; +} +// +wxString Storage::getStrGroup(wxString source) { + int i = 0; + int l = source.Length(); + wxString n; + bool quote = false; + while (i < l) { + char c = source[i++]; + if (c >= '0' && c <= '9' && !quote) continue; + if (c == '"') { + + if ((i < l) && source[i] == c) { + n.Append(c, 2); + //n.Append(c); + i++; + continue; + } + else + quote = !quote; + } + n.Append(c); + } + return n; +} +int Storage::GetTotalCountGroup(int rowfilter) { + if (rowfilter != m_cacheIndex) getLineToCache(rowfilter, true); + MyHashToRow::const_iterator it = hashKeyTotal.find(m_cacheLine.hash); + int a = 0; + if (it != hashKeyTotal.end()) + { + a = hashKeyTotal[m_cacheLine.hash]; + } + if (a==0) { + int h = m_cacheLine.hash; + int lastrow=hashKeyToRow[h]; + a = 0; + m_cacheIndex = -1; + while (lastrow!=-1) { + getLineToCache(lastrow, false); + lastrow = m_cacheLine.prevRowGroup; + a++; + } + hashKeyTotal[h]=a; + m_cacheIndex = -1; + } + return a; +} +void Storage::ClearCount(int rowfilter) { + if (rowfilter != m_cacheIndex) getLineToCache(rowfilter, true); + MyHashToRow::const_iterator it = hashKeyTotal.find(m_cacheLine.hash); + int cnt = hashKeyToCount[m_cacheLine.hash]; + int a = 0; + if (it != hashKeyTotal.end()) + { + a = hashKeyTotal[m_cacheLine.hash]; + } + hashKeyTotal[m_cacheLine.hash] = a + cnt; + hashKeyToCount[m_cacheLine.hash] = 0; +} +int Storage::getCountGroup(int row) { + + wxString valField = GetFieldStorage(row, MyConst::colField::logtime, false); + int h = m_cacheLine.hash; + int c = hashKeyToCount[h]; + return c; +} +bool Storage::checkFilter(Line& l) { + + if (filterload.size() > 0) { + wxString sf; + bool rez=true; + int last = filterload.size() - 1; + int i = 0; + for (auto fl:filterload) { + if (fl.col == -1) { + if (rez || (last==i)) break; + rez = true; + } + else { + if (rez) { + sf = get_field(l, (MyConst::colField)fl.col); + int flags = fl.flags; + bool reverse = (flags & FL_REVERSE) == FL_REVERSE; + + if ((flags & FL_CONTAINS) == 0) rez = sf == fl.val; + else + rez = sf.Contains(fl.val); + if (reverse) rez = !rez; + } + } + i++; + + } + return rez; + } + return false; +} +bool Storage::AddLineTextCSV(const wxString& strcsv) { + Line st = getLineParse(strcsv, true); + + if (checkFilter(st)) { + rowsignore++; + return false; + } + rowsadd++; + wxString msg = st.text.substr(st.logMessage.s, st.logMessage.l); + if (st.icon > MyConst::iconIndex::war) SetErrMsgFlag(true); + + wxString gstr = getStrGroup(msg); + int h = getHashString(gstr); + MyHashToRow::const_iterator it = hashKeyToRow.find(h); + prevRow = -1; + int cnt = 1; + faddgroup = true; + if (it != hashKeyToRow.end()) + { + faddgroup = false; + prevRow = it->second; + cnt = hashKeyToCount[h]; + cnt++; + } + int nextrow = storage.size(); + st.prevRowGroup = prevRow; + st.hash = h; + hashKeyToRow[h] = nextrow; + hashKeyToCount[h] = cnt; + storage.push_back(st); + return true; +} + + diff --git a/utils/log/StorageModel.cpp b/utils/log/StorageModel.cpp new file mode 100644 index 0000000..0e67259 --- /dev/null +++ b/utils/log/StorageModel.cpp @@ -0,0 +1,486 @@ +#include "pgAdmin3.h" + + +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#include "wx/dataview.h" +#include "log/Storage.h" +#include "log/StorageModel.h" + +// ---------------------------------------------------------------------------- +// resources +// ---------------------------------------------------------------------------- + +#include "log/null.xpm" +#include "log/log_xpm.xpm" +#include "log/war_xpm.xpm" +#include "log/errorl_xpm.xpm" +#include "log/fatal_xpm.xpm" +#include "log/panic_xpm.xpm" +#include "log/user_xpm.xpm" +#include "log/wx_small.xpm" + +// ---------------------------------------------------------------------------- +// MyCustomRendererText +// ---------------------------------------------------------------------------- + +class MyCustomRendererText : public wxDataViewCustomRenderer +{ +public: + StorageModel::cols col; + Storage* st; + int row; + int countRows = 0; + // This renderer can be either activatable or editable, for demonstration + // purposes. In real programs, you should select whether the user should be + // able to activate or edit the cell and it doesn't make sense to switch + // between the two -- but this is just an example, so it doesn't stop us. + explicit MyCustomRendererText(wxDataViewCellMode mode, StorageModel::cols column) + : wxDataViewCustomRenderer("string", mode, wxALIGN_LEFT) + { + EnableEllipsize(wxELLIPSIZE_END); + col = column; + st = NULL; + } + + virtual bool Render(wxRect rect, wxDC* dc, int state) wxOVERRIDE + { + //dc->SetBrush( *wxLIGHT_GREY_BRUSH ); + dc->SetBrush(*wxYELLOW_BRUSH); + dc->SetPen(*wxTRANSPARENT_PEN); + rect.Deflate(2); + wxRect orig = rect; + + //dc->DrawRoundedRectangle( rect, 3 ); + + wxString s = m_value, t; + wxString rest; + wxString* pointer = &rest; + int x = 0, y = 0; + //dc->GetMultiLineTextExtent(m_value); + wxArrayInt arr; + wxSize h = dc->GetTextExtent("H"); + dc->GetPartialTextExtents(m_value, arr); + bool ex = false; + int i = 0; + int startX = 0; + + wxRect rectCol; + + while (i < s.Len()) { + + t = ""; + x = 0; + bool inquote = false; + rectCol.x = rect.x; + rectCol.y = rect.y; + rectCol.height = h.GetHeight(); + rectCol.width = 0; + while (i < s.Len()) + { + if (s[i] == '\n') { + startX = arr[i]; + i++; + break; + } + if (s[i] == '"') { + if (inquote) { + // + rectCol.width = arr[i] - rectCol.x; + rectCol.x = rectCol.x - startX + rect.x; + dc->DrawRoundedRectangle(rectCol, 3); + rectCol.x = arr[i]; + rectCol.width = 0; + } + else + rectCol.x = arr[i]; + + inquote = !inquote; + } + t += s[i]; + i++; + } + RenderText(t, + x, // no offset + //wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), + rect, + dc, + state); + rect.y = rect.y + h.GetHeight() + 0; + rect.height = rect.height - (h.GetHeight() + 0); + + } + + if (countRows > 0 && col == StorageModel::cols::Col_Host) { + dc->SetBrush(*wxGREEN_BRUSH); + wxString str = wxString::Format("%d", countRows); + wxSize sz = dc->GetTextExtent(str); + sz.SetWidth(sz.GetX() + 3); + orig.SetLeft(orig.GetLeft() + (orig.GetWidth() - sz.GetWidth())); + orig.SetHeight(sz.GetHeight()); + orig.SetWidth(sz.GetWidth()); + dc->DrawRoundedRectangle(orig, 2); + RenderText(str, + 1, // no offset + //wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), + orig, + dc, + state); + + + } + + return true; + + RenderText(m_value, + 0, // no offset + //wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), + rect, + dc, + state); + return true; + } + + virtual bool ActivateCell(const wxRect& WXUNUSED(cell), + wxDataViewModel* WXUNUSED(model), + const wxDataViewItem& WXUNUSED(item), + unsigned int WXUNUSED(col), + const wxMouseEvent* mouseEvent) wxOVERRIDE + { + wxString position; + if (mouseEvent) + position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); + else + position = "from keyboard"; + wxLogMessage("MyCustomRendererText ActivateCell() %s", position); + return false; + } + + virtual wxSize GetSize() const wxOVERRIDE + { + wxSize txtSize = GetTextExtent(m_value); + //wxSize txtSize = GetDC()->GetMultiLineTextExtent(GetMultiLineTextExtent); + //wxSize txtSize = wxDataViewRenderer::GetDC()->GetMultiLineTextExtent(m_value); + int lines = m_value.Freq('\n') + 1; + if (lines > 1) { + wxString position; + position = wxString::Format("lines %d,hieght 1 row %d full h=%d", lines, txtSize.GetHeight(), txtSize.GetHeight() * lines + 1 * lines); + // wxLogMessage("MyCustomRendererText GetSize() %s", position); + txtSize.SetHeight(txtSize.GetHeight() * lines + 1 * lines); + } + else + txtSize.SetHeight(-1); + txtSize.SetWidth(-1); + return txtSize; + + //return GetView()->FromDIP(wxSize(60, 20)); + } + + virtual bool SetValue(const wxVariant& value) wxOVERRIDE + { + m_value = value.GetString(); + if (!st) { + StorageModel* m = dynamic_cast(GetView()->GetModel()); + st = m->getStorage(); + } + if (st->IsGroupFilter()) { + row = st->getLastRowIndex(); + countRows = st->getCountGroup(row); + } + else countRows = -1; + + return true; + } + + virtual bool GetValue(wxVariant& WXUNUSED(value)) const wxOVERRIDE { return true; } + +#if wxUSE_ACCESSIBILITY + virtual wxString GetAccessibleDescription() const wxOVERRIDE + { + return m_value; + } +#endif // wxUSE_ACCESSIBILITY + + virtual bool HasEditorCtrl() const wxOVERRIDE { return true; } + + virtual wxWindow* + CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) wxOVERRIDE + { + wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, + labelRect.GetPosition(), + labelRect.GetSize(), + wxTE_PROCESS_ENTER); + text->SetInsertionPointEnd(); + + return text; + } + + virtual bool + GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) wxOVERRIDE + { + wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); + if (!text) + return false; + wxString sel = text->GetStringSelection(); + if (sel.IsEmpty()) return false; + value = sel; + + return true; + } + +private: + wxString m_value; +}; + +// ---------------------------------------------------------------------------- +// StorageModel +// ---------------------------------------------------------------------------- + +static int my_sort_reverse(int* v1, int* v2) +{ + return *v2 - *v1; +} + +static int my_sort(int* v1, int* v2) +{ + return *v1 - *v2; +} + +#define INITIAL_NUMBER_OF_ITEMS 0 + +void StorageModel::BuildColumns(MyDataViewCtrl* ctrl) { + wxDataViewColumn* const colIconText = new wxDataViewColumn + ( + "Severity", + new wxDataViewIconTextRenderer(), + StorageModel::Col_ToggleIconText, + wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER_VERTICAL + ); + colmap[StorageModel::Col_ToggleIconText] = MyConst::colField::logSqlstate; + ctrl->AppendColumn(colIconText); + + ctrl->AppendColumn( + new wxDataViewColumn("logTime", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_LogTime), + StorageModel::Col_LogTime, + wxCOL_WIDTH_AUTOSIZE, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_LogTime] = MyConst::colField::logtime; + + ctrl->AppendColumn( + new wxDataViewColumn("UserName", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_User), + StorageModel::Col_User, + wxCOL_WIDTH_AUTOSIZE, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_User] = MyConst::colField::loguser; + ctrl->AppendColumn( + new wxDataViewColumn("DbName", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_Db), + StorageModel::Col_Db, + 30, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_Db] = MyConst::colField::logdb; + ctrl->AppendColumn( + new wxDataViewColumn("Pid", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_PID), + StorageModel::Col_PID, + 60, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_PID] = MyConst::colField::logpid; + ctrl->AppendColumn( + new wxDataViewColumn("Host", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_Host), + StorageModel::Col_Host, + 90, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_Host] = MyConst::colField::loghost; + ctrl->AppendColumn( + new wxDataViewColumn("AppName", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_App), + StorageModel::Col_App, + 100, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_App] = MyConst::colField::logappname; + ctrl->AppendColumn( + new wxDataViewColumn("Hint", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_Hint), + StorageModel::Col_Hint, + 90, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_Hint] = MyConst::colField::logHint; + ctrl->AppendColumn( + new wxDataViewColumn("Detail", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_Detail), + StorageModel::Col_Detail, + 60, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_Detail] = MyConst::colField::logDetail; + + ctrl->AppendColumn( + new wxDataViewColumn("Message", + new MyCustomRendererText(wxDATAVIEW_CELL_EDITABLE, StorageModel::Col_Message), + StorageModel::Col_Message, + wxCOL_WIDTH_AUTOSIZE, + wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE + )); + colmap[StorageModel::Col_Message] = MyConst::colField::logMessage; + + +} +#include +#include "log/filter_xpm.xpm" +StorageModel::StorageModel(MyDataViewCtrl* view) : + wxDataViewVirtualListModel(INITIAL_NUMBER_OF_ITEMS) +{ + m_view = view; + //m_icon[0] = wxIcon(null_xpm); + m_icon[MyConst::iconIndex::log] = wxIcon(log_xpm); + m_icon[MyConst::iconIndex::war] = wxIcon(war_xpm); + m_icon[MyConst::iconIndex::user] = wxIcon(user_xpm); + m_icon[MyConst::iconIndex::error] = wxIcon(errorl_xpm); + m_icon[MyConst::iconIndex::fatal] = wxIcon(fatal_xpm); + m_icon[MyConst::iconIndex::panic] = wxIcon(panic_xpm); + bitmapflt = wxBitmap((wxIcon(filter_xpm))); + store = new Storage(); +} +bool StorageModel::setFilter(int col, wxString val, int flags, MyDataViewCtrl* view) { + + bool r = store->SetFilter(colmap[col], val, flags); + Reset(store->getCountFilter()); + if (col != -1) { + wxDataViewColumn* vc = view->GetColumn(col); + vc->SetBitmap(bitmapflt); + } + return r; +} +int StorageModel::testFilter(int col, int position = 0) { + int idx = 0; + idx = store->testFilter(colmap[col], position); + return idx; +} +void StorageModel::ApplyFilter() { + store->ApplyFilter(); + Reset(store->getCountFilter()); +} + +void StorageModel::DropColFilter(int index) { + if (index >= 0) store->DropColFilter(index); +} +bool StorageModel::Prepend(const wxString& text) +{ + //m_toggleColValues.insert(m_toggleColValues.begin(), 0); + //m_textColValues.Insert(text, 0); + bool add=store->AddLineTextCSV(text); + if (!add) return false; + int row = store->getCountStore() - 1; + wxString val = store->GetFieldStorage(row, MyConst::colField::loguser, false); + IncCountFreq(StorageModel::Col_User, val); + IncCountFreq(StorageModel::Col_Db, store->GetFieldStorage(row, MyConst::colField::logdb, false)); + IncCountFreq(StorageModel::Col_Host, store->GetFieldStorage(row, MyConst::colField::loghost, false)); + val = store->GetFieldStorage(row, MyConst::colField::logappname, false); + //if (val.IsEmpty()) val = store->GetFieldStorage((int)row, MyConst::colField::logbtype,false); + IncCountFreq(StorageModel::Col_App, val); + IncCountFreq(StorageModel::Col_PID, store->GetFieldStorage(row, MyConst::colField::logpid, false)); + IncCountFreq(StorageModel::Col_Hint, store->GetFieldStorage(row, MyConst::colField::logHint, false)); + val = store->GetFieldStorage(row, MyConst::colField::logSqlstate, false); + IncCountFreq(StorageModel::Col_ToggleIconText, val); + + + if (store->ApplyFilter(row)) { + RowAppended(); + //RowPrepended(); + return true; + } + if (store->IsGroupFilter()) { + + } + return false; + +} +void StorageModel::DeleteItem(const wxDataViewItem& item) +{ + unsigned int row = GetRow(item); + + RowDeleted(row); +} + +void StorageModel::DeleteItems(const wxDataViewItemArray& items) +{ + unsigned i; + wxArrayInt rows; +} + +void StorageModel::AddMany() +{ + Reset(GetCount() + 1000); +} + +void StorageModel::GetValueByRow(wxVariant& variant, + unsigned int row, unsigned int col) const +{ + MyConst::colField fldcsv = colmap[col]; + wxString val; + val = store->GetField((int)row, fldcsv); + if (col == StorageModel::Col_ToggleIconText) { + int i = store->GetSeverityIndex((int)row); + variant << wxDataViewIconText(val, m_icon[i]); + return; + } + else if (col == StorageModel::Col_App) { + //if (val.IsEmpty()) val = store->GetField((int)row, MyConst::colField::logbtype); + } + else if (col == StorageModel::Col_User) { + + }; + + variant = val; +} + +bool StorageModel::GetAttrByRow(unsigned int row, unsigned int col, + wxDataViewItemAttr& attr) const +{ + + //attr.SetBackgroundColour(*wxLIGHT_GREY); + wxColor c = store->GetBgColorLine((int)row); + if (c.IsOk()) { + attr.SetBackgroundColour(c); + return true; + } + return false; +} + +bool StorageModel::SetValueByRow(const wxVariant& variant, + unsigned int row, unsigned int col) +{ + wxString flt = variant.GetString(); + int flags = FL_CONTAINS; + if (flt[0] == '!') { + flags = flags | FL_REVERSE; + flt = flt.substr(1); + } + setFilter(col, flt, flags, m_view); + + return false; +} diff --git a/x64/Release_(3.0)/pgAdmin3.exe b/x64/Release_(3.0)/pgAdmin3.exe index 8b16861..0f61388 100644 Binary files a/x64/Release_(3.0)/pgAdmin3.exe and b/x64/Release_(3.0)/pgAdmin3.exe differ