
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/regex.h>
#include <wx/xrc/xmlres.h>
#include <wx/image.h>
#include <wx/textbuf.h>
#include <wx/sysopt.h>

// wxAUI
#include <wx/aui/aui.h>

// 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 "utils/utffile.h"
#include "utils/misc.h"

#include <wx/arrimpl.cpp>
#ifdef WIN32
#include <wx/msw/ole/automtn.h>
#endif


WX_DEFINE_OBJARRAY(RemoteConnArray2);


wxBEGIN_EVENT_TABLE(frmLog, pgFrame)
// test
EVT_MENU(MNU_FIND_TEXT, frmLog::OnFind)
EVT_MENU(MNU_SEND_MAIL, frmLog::OnSendMail)
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_BUTTON(ID_ADD_UFilter, frmLog::OnAddUFilter)
EVT_BUTTON(ID_DEL_UFilter, frmLog::OnDelUFilter)
EVT_BUTTON(ID_EXTENDED_LOG, frmLog::OnExtendedLog)
EVT_TEXT_ENTER(ID_COUNT_FILES, frmLog::OnTextChange)
EVT_BUTTON(ID_HELP_LOG, frmLog::OnHelp)
EVT_COMBOBOX(ID_CBOX_UFilter, frmLog::OnChangeUFilter)
EVT_COMBOBOX(ID_CBOX_SMART, frmLog::OnChangeSmart)
EVT_SET_FOCUS(frmLog::OnSetFocus)
EVT_CLOSE(frmLog::OnClose)
EVT_KILL_FOCUS(frmLog::OnKillFocus)
EVT_ACTIVATE(frmLog::OnActivate)
wxEND_EVENT_TABLE()

//#include <wx/arrimpl.cpp>
//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);

}
void frmLog::OnClose(wxCloseEvent& event) {
    if (event.CanVeto()&& detail->IsChecked())
    {
        my_view->setGroupMode(true);
        detail->SetValue(false);
    //detail->Enable(event.IsChecked());
        event.Veto();
        return;
    }
    DBthread->DoTerminate();
    while (!DBthread->isReadyRows()) {
        wxThread::Sleep(50);
        wxYield();
    }
    DBthread->GoReadRows();
    
    wxThread::Sleep(50);
    event.Skip();
}

// Class declarations
void frmLog::OnClearAllFilter(wxCommandEvent& event) {

    my_view->ClearAllFilter(false);
}
void frmLog::OnAddFilterIgnore(wxCommandEvent& event) {
    wxString Fname = "LoadSkip";
    my_view->ModUserFilter(Fname, "AddUserFilter", listUserFilter, contentFilter);
    //my_view->AddFilterIgnore(Fname);
}
void frmLog::OnAddUFilter(wxCommandEvent& event) {
    wxString txt = listUserFilter->GetValue();
    my_view->ModUserFilter(txt, "AddUserFilter", listUserFilter, contentFilter);

}
void frmLog::OnDelUFilter(wxCommandEvent& event) {
    my_view->ModUserFilter("", "RemoveFilter", listUserFilter, contentFilter);
}
void frmLog::OnChangeUFilter(wxCommandEvent& event) {
    if (event.GetSelection() >= 0) {
        my_view->ModUserFilter("", "ChangeFilter", listUserFilter, contentFilter);
    }
}
void frmLog::OnExtendedLog(wxCommandEvent& event) {
    //my_view->ModUserFilter("", "RemoveFilter", listUserFilter, contentFilter);
    StorageModel* m = dynamic_cast<StorageModel*>(my_view->GetModel());
    m->DeleteAll();
    bool b = true;
    b=group->GetValue();
    my_view->setGroupMode(b);
    detail->SetValue(false);
    my_view->ViewGroup(false);

}
void frmLog::OnTextChange(wxCommandEvent& event) {
    //my_view->ModUserFilter("", "RemoveFilter", listUserFilter, contentFilter);
    wxTextCtrl* t = (wxTextCtrl*)event.GetEventObject();
    wxString text = t->GetValue();
    int count = 1;
    if (wxSscanf(text, "%d", &count) > 0) {
        bool isOk=DBthread->SetLimitFiles(count);
        OnExtendedLog(event);
    };
}

void frmLog::OnChangeSmart(wxCommandEvent& event) {
    int n = event.GetSelection();
    if (n >= 0) {
        listUserFilter->SetSelection(n);
        my_view->ModUserFilter("", "ChangeFilter", listUserFilter, contentFilter);
    }
}
void frmLog::OnSendMail(wxCommandEvent& event) {
    //wxMessageBox("send mail");
    wxDataViewItem item;
    item = my_view->GetCurrentItem();
    if (!item.IsOk() ) {
        return;
    }
    wxVariant v, t;
    StorageModel* m = dynamic_cast<StorageModel*>(my_view->GetModel());
    Storage* st = m->getStorage();
    wxArrayString a;
    int r = m->GetRow(item);
    if (r != -1) {
        a=st->GetAllFields(r, st->IsFilter());
    }
    else return;

    wxString str = "";
#ifdef DEBUG
    wxString fn = "C:\\Users\\lsv\\Source\\Repos\\pgadmin64\\pgadmin\\x64\\Debug_(3.0)\\testhtml.txt" ;
#else
    wxString fn = "mail.template";
#endif // DEBUG
    wxUtfFile file(fn, wxFile::read, wxFONTENCODING_UTF8);
    if (file.IsOpened())
    {
        file.Read(str);
        file.Close();
        wxStringTokenizer tk(str, "\n", wxTOKEN_DEFAULT);
        wxString cc;
        wxString to;
        wxString html;
        wxString templat;
        while (tk.HasMoreTokens())
        {
            wxString l = tk.GetNextToken();
            if (l.StartsWith("Cc=")) { cc = l.After('='); continue; }
            if (l.StartsWith("To=")) { to = l.After('='); continue; }
            if (l.StartsWith("<tr>")) {
                wxString le;
                wxString r;
                for (int i = 0; i < a.Count(); i++) {
                    templat = l;
                    le = escapeHtml(a[i++],false);
                    r = escapeHtml(a[i],false);
                    int co = templat.Replace("$1", le);
                    co += templat.Replace("$2", r);
                    html.Append(templat);
                }
                continue;
            }
            html.Append(l);
        }
        #ifdef WIN32
        wxAutomationObject oObject;
        if (oObject.GetInstance("Outlook.Application")) {
            wxAutomationObject msg;
            wxVariant n[1];
            wxString strI;
            int i = 0;
            strI << i;
            n[0] = wxVariant(strI);
            bool rez = oObject.GetObject(msg, "CreateItem", 1, n);
            if (rez) {
                //oObject.PutProperty("Visible", true);
                msg.PutProperty("Subject", "Error ");
                msg.PutProperty("BodyFormat", 2);
                msg.PutProperty("To", to);
                //
                msg.PutProperty("Cc", cc);
                msg.PutProperty("HTMLBody", html);
                msg.CallMethod("Display");
            }
            //oObject.PutProperty("ActiveCell.Font.Bold", @true);

        }
        #endif
    }
}
void frmLog::OnFind(wxCommandEvent& event) {

}
void frmLog::OnHelp(wxCommandEvent& event) {

    wxMessageBox(wxString::FromUTF8("Для включения фильтра нужно:\n"
        " Щелкнуть правой кнопкой мыши по полю.Для инверсии фильтра нужно удерживать Ctrl.\n"
        " Выбрать значение в контекстном меню заголовка колонки.\n"
        " Там отображаются 20 самых частых значения в колонке с указанием количества этих значений.\n"
        " Ввести в поле значения для фильтра, выделить это значение и нажать Enter.\n"
        " Для фильтра используется только выделенный текст.\n"
        " Такой фильтр будет работать на поиск выделенного вхождения в поле.\n"
        " Если в выделенной строке первым символом будет \"!\" то фильтр инверсируется.\n"
        "\n"
        "Shift+KeyUP,Shift+KeyDOWN - переход на запись с тем же sql_state.\n"
        "Alt+KeyUP,Alt+KeyDOWN     - переход на запись с другим sql_state.\n"
        "Ctrl + S                  - отправка строки лога по почте Outlook. \n"
        "                          Шаблон письма в файле mail.template в первых двух строках шаблона можно указать адреса которые будут подставляться в письмо.\n"
    ));
}

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* MyThread::Entry() {

    while (!m_exit)
    {
        {
            wxMutexLocker lock(s_mutexDBReading);
            m_addNewRows.Clear();
            m_serversName.Clear();
            m_startRowsSevers.Clear();
            getFilename();
        }
        
        s_goRead.Wait();
        if (m_exit) break;
    }

    return NULL;
}
void MyThread::ResetInfoFiles() {
    readfiles.clear();
}
bool MyThread::SetLimitFiles(int limit) {
    if (limit != limitfiles) {
        limitfiles = limit;
        ResetInfoFiles();
        return true;
    }
    return false;
}
wxString MyThread::AppendNewRows(MyDataViewCtrl* my_view, Storage* st) {
    //Storage* st = m_storage_model->getStorage();
    //int rows = st->getCountStore();
    int ra, ri;
    st->ClearRowsStat();
    my_view->Freeze();
    for (size_t i = 0; i < m_startRowsSevers.GetCount(); i++) {
        if (m_exit) return "exit";
        int sr = m_startRowsSevers[i];
        int er = m_addNewRows.GetCount();
        if ((i+1)< m_startRowsSevers.GetCount()) er= m_startRowsSevers[i+1];
        if (sr == er) continue;
        st->SetHost(m_serversName[i]);
        for (int k = sr; k < er; k++) {
            if (m_exit) return "exit";
            wxString ss = m_addNewRows[k];
            if (ss.IsEmpty())
                continue;
            my_view->AddRow(ss);
        }
    }
    st->GetRowsStat(ra, ri);
    my_view->Thaw();
    //int newrows = st->getCountStore();
    //if (loglen !=logfileLength) 
    if (st->GetErrMsgFlag()) {
        m_seticon=true;
    }
    return wxString::Format("Add rows %d ignore %d. View rows ", ra, ri);

}
void MyThread::getFilename() {
    pgSet* set;
    namepage = "";
    m_seticon = false;
    wxString mask = ".csv$";
    for (size_t i = 0; i < m_conArray.GetCount(); i++) {
        if (m_exit) break;
        RemoteConn2* po = (RemoteConn2*) m_conArray[i];
        wxString dbname = po->conn->GetDbname();
        if (!namepage.IsEmpty()) namepage += ",";
        m_serversName.Add(po->conn->GetHostName());
        m_startRowsSevers.Add(m_addNewRows.GetCount());
        if (!po->conn->IsAlive()) {
            wxDateTime n = wxDateTime::Now();
            m_seticon = true;
            if (po->nextrun < n) {
                if (!po->conn->Reconnect(false))
                {
                    wxTimeSpan sp(0, 2);
                    po->nextrun = wxDateTime::Now() + sp;
                    namepage += " " + dbname;
                    continue;
                }
            }
            else {
                namepage += " " + dbname;
                continue;
            }


        }
        wxString sql = wxString::Format(
        "select * from ( \
            select current_setting('log_directory') || '/' || name filename, modification filetime, size len \
            FROM pg_ls_logdir()  where name ~ %s and name !~'db.csv$' ORDER BY modification DESC limit %d) l order by  filetime ASC", po->conn->qtDbString(mask), limitfiles
        );


        set = po->conn->ExecuteSet(sql);
        if (set)
        {

            //logfileTimestamp = set->GetDateTime(wxT("filetime"));
            namepage += dbname;
            while (!set->Eof()) {
                
                wxString fn = set->GetVal(wxT("filename"));
                if (fn.AfterLast(sepPath).CmpNoCase("db.csv")==0) {
                    set->MoveNext();   
                    continue; 
                }
                wxString key = wxString::Format("%lu_server_%s",i,fn);
                info_files inf;
                long len= set->GetLong(wxT("len"));
                if (auto it = readfiles.find(key); it != readfiles.end()) {
                    inf = it->second;
                }
                else {
                    inf.filename = fn;
                    inf.readlastposition = 0;
                    inf.size = len;
                }
                //len[i] = set->GetLong(wxT("len"));
                
                //m_storage_model->getStorage()->SetHost(m_conArray[i].conn->GetHostName());
                //m_startRowsSevers[i] = m_addNewRows.GetCount();
                
                if (inf.readlastposition != len) {
                    inf.size = len;
                    readLogFile(inf, po->conn);
                    readfiles[key] = inf;
                }
                set->MoveNext();
            }
            delete set;
            
        }
    }
    sendText("LOAD OK");
}
void MyThread::readLogFile(info_files &inf,
    //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.
    wxString logfileName = inf.filename;
    long logfileLength = inf.readlastposition;
    bool csv_log_format = logfileName.Right(4) == wxT(".csv");

    if (csv_log_format && inf.savedPartialLine.length() > 0)
    {
        if (logfileLength == 0)  // Starting at beginning of log file
            inf.savedPartialLine.clear();
        else
            line = inf.savedPartialLine;
    }
    wxString funcname = "pg_read_binary_file(";
    while (inf.size > logfileLength)
    {
        //statusBar->SetStatusText(_("Reading log from server..."));
        if (m_exit) return;
        pgSet* set = conn->ExecuteSet(wxT("SELECT ") + funcname +
            conn->qtDbString(logfileName) + wxT(", ") + NumToStr(logfileLength) + wxT(", 50000,true)"));
        if (!set)
        {
            conn->IsAlive();
            return;
        }
        if (set->NumRows() == 0 || set->NumCols() ==0 || set->GetVal(0).IsNull()) {
            delete set;
            return;
        }

        char* raw1 = set->GetCharPtr(0);

        if (!raw1 || !*raw1)
        {
            delete set;
            break;
        }
        char* raw;
        unsigned char m[50001];
        if (true) {

            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;
        wxString host = conn->GetHostName();
        //status->SetLabelText(wxString::Format("%s Load bytes %ld", host, logfileLength));
        sendText(wxString::Format("%s file: %s Load bytes %ld", host, logfileName, 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 = host+" The server log contains entries in multiple encodings and cannot be displayed by pgAdmin.";
            //wxMessageBox(msgstr);
            sendText(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.


                //my_view->AddRow(str.Trim());
                m_addNewRows.Add(str.Trim());
            }

            //my_view->Thaw();
        }
        else
        {
        }
    }

    inf.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)
        {
            inf.savedPartialLine = line;    // Save partial log line for next read of the data file.
            line.Clear();
        }
        else
            //my_view->AddRow(line.Trim());
            m_addNewRows.Add(line.Trim());
    }
    inf.readlastposition = logfileLength;

}

void frmLog::OnAddLabelTextThread(wxThreadEvent& event) {
    wxString s=event.GetString();
    if (s == "LOAD OK") {
        //m_timer.Stop();
        //wxTimerEvent ev;
    //	OnTimer(ev);
        //m_timer.Start(timerInterval);
        return;
    }
    if (!msgtext.IsEmpty() && !s.IsEmpty()) s += " : ";
    s += msgtext;
    status->SetLabelText(s);
}

void frmLog::OnTimer(wxTimerEvent& event) {
    
    if (!DBthread->isReadyRows()) return;
    wxString rez=DBthread->AppendNewRows(my_view, m_storage_model->getStorage());
    msgtext = wxString::Format("%s%d", rez, m_storage_model->GetRowCount());
    status->SetLabelText(msgtext);
    if (DBthread->IsIconError()) {
        seticon(true);
    }
    wxString namepage= DBthread->getNamePage();

    if (namepage.IsEmpty()) namepage = "not connect";
    if (m_notebook->GetPageText(0) != namepage) m_notebook->SetPageText(0, namepage);
    DBthread->GoReadRows();

}
#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);
}
wxSize MywxAuiDefaultTabArt::GetTabSize(wxDC& dc,
    wxWindow* wnd,
    const wxString& caption,
    const wxBitmapBundle& bitmap,
    bool WXUNUSED(active),
    int close_button_state,
    int* x_extent)
{
    wxCoord measured_textx, measured_texty, tmp;

    dc.SetFont(m_normalFont);
    dc.GetTextExtent(caption, &measured_textx, &measured_texty);

    dc.GetTextExtent(wxT("ABCDEFXj"), &tmp, &measured_texty);

    // add padding around the text
    wxCoord tab_width = measured_textx;
    wxCoord tab_height = measured_texty;

    // if the close button is showing, add space for it
    if (close_button_state != wxAUI_BUTTON_STATE_HIDDEN)
    {
        // increase by button size plus the padding
        tab_width += m_activeCloseBmp.GetBitmapFor(wnd).GetLogicalWidth() + wnd->FromDIP(3);
    }

    // if there's a bitmap, add space for it
    if (bitmap.IsOk())
    {
        // we need the correct size of the bitmap to be used on this window in
        // logical dimensions for drawing
        const wxSize bitmapSize = bitmap.GetPreferredLogicalSizeFor(wnd);

        // increase by bitmap plus right side bitmap padding
        tab_width += bitmapSize.x + wnd->FromDIP(3);
        tab_height = wxMax(tab_height, bitmapSize.y);
    }

    // add padding
    wxSize padding = wnd->FromDIP(wxSize(16, 10));
    tab_width += padding.x;
    tab_height += padding.y;

    if (m_flags & wxAUI_NB_TAB_FIXED_WIDTH)
    {
        tab_width = m_fixedTabWidth;
    }

    *x_extent = tab_width;

    return wxSize(tab_width, tab_height);
}

void MywxAuiDefaultTabArt::DrawTab(wxDC& dc,
    wxWindow* wnd,
    const wxAuiNotebookPage& page,
    const wxRect& in_rect,
    int close_button_state,
    wxRect* out_tab_rect,
    wxRect* out_button_rect,
    int* x_extent)
{
    wxCoord normal_textx, normal_texty;
    wxCoord selected_textx, selected_texty;
    wxCoord texty;

    // if the caption is empty, measure some temporary text
    wxString caption = page.caption;
    if (caption.empty())
        caption = wxT("Xj");

    dc.SetFont(m_normalFont);
    dc.GetTextExtent(caption, &selected_textx, &selected_texty);

    dc.SetFont(m_normalFont);
    dc.GetTextExtent(caption, &normal_textx, &normal_texty);

    // figure out the size of the tab
    wxSize tab_size = GetTabSize(dc,
        wnd,
        page.caption,
        page.bitmap,
        page.active,
        close_button_state,
        x_extent);

    wxCoord tab_height = m_tabCtrlHeight - 3;
    wxCoord tab_width = tab_size.x;
    wxCoord tab_x = in_rect.x;
    wxCoord tab_y = in_rect.y + in_rect.height - tab_height;


    caption = page.caption;


    // select pen, brush and font for the tab to be drawn

    if (page.active)
    {
        dc.SetFont(m_normalFont);
        texty = selected_texty;
    }
    else
    {
        dc.SetFont(m_normalFont);
        texty = normal_texty;
    }


    // create points that will make the tab outline

    int clip_width = tab_width;
    if (tab_x + clip_width > in_rect.x + in_rect.width)
        clip_width = (in_rect.x + in_rect.width) - tab_x;

    // since the above code above doesn't play well with WXDFB or WXCOCOA,
    // we'll just use a rectangle for the clipping region for now --
    dc.SetClippingRegion(tab_x, tab_y, clip_width + 1, tab_height - 3);


    wxPoint border_points[6];
    
        border_points[0] = wxPoint(tab_x, tab_y + tab_height - 4);
        border_points[1] = wxPoint(tab_x, tab_y + 2);
        border_points[2] = wxPoint(tab_x + 2, tab_y);
        border_points[3] = wxPoint(tab_x + tab_width - 2, tab_y);
        border_points[4] = wxPoint(tab_x + tab_width, tab_y + 2);
        border_points[5] = wxPoint(tab_x + tab_width, tab_y + tab_height - 4);
    
    // TODO: else if (m_flags &wxAUI_NB_LEFT) {}
    // TODO: else if (m_flags &wxAUI_NB_RIGHT) {}

    int drawn_tab_yoff = border_points[1].y;
    int drawn_tab_height = border_points[0].y - border_points[1].y;

    bool isdark = wxSystemSettings::GetAppearance().IsUsingDarkBackground();

    wxColor back_color = m_baseColour;
    if (page.active)
    {
        // draw active tab

        // draw base background color
        wxRect r(tab_x, tab_y, tab_width, tab_height);
        dc.SetPen(wxPen(m_activeColour));
        dc.SetBrush(wxBrush(m_activeColour));
        dc.DrawRectangle(r.x + 1, r.y + 1, r.width - 1, r.height - 4);

        // this white helps fill out the gradient at the top of the tab
        wxColor gradient = *wxWHITE;
        if (isdark)
        {
            //dark mode, we go darker
            gradient = m_activeColour.ChangeLightness(70);
        }
        back_color = gradient;

        dc.SetPen(wxPen(gradient));
        dc.SetBrush(wxBrush(gradient));
        dc.DrawRectangle(r.x + 2, r.y + 1, r.width - 3, r.height - 4);

        // these two points help the rounded corners appear more antialiased
        dc.SetPen(wxPen(m_activeColour));
        dc.DrawPoint(r.x + 2, r.y + 1);
        dc.DrawPoint(r.x + r.width - 2, r.y + 1);

        // set rectangle down a bit for gradient drawing
        r.SetHeight(r.GetHeight() / 2);
        r.x += 2;
        r.width -= 3;
        r.y += r.height;
        r.y -= 2;

        // draw gradient background
        wxColor top_color = gradient;
        wxColor bottom_color = m_activeColour;
        dc.GradientFillLinear(r, bottom_color, top_color, wxNORTH);
    }
    else
    {
        // draw inactive tab

        wxRect r(tab_x, tab_y + 1, tab_width, tab_height - 3);

        // start the gradient up a bit and leave the inside border inset
        // by a pixel for a 3D look.  Only the top half of the inactive
        // tab will have a slight gradient
        r.x += 3;
        r.y++;
        r.width -= 4;
        r.height /= 2;
        r.height--;

        // -- draw top gradient fill for glossy look
        wxColor top_color = m_baseColour;
        wxColor bottom_color = top_color.ChangeLightness(160);
        if (isdark)
        {
            //dark mode, we go darker
            top_color = m_activeColour.ChangeLightness(70);
            bottom_color = m_baseColour;
        }

        dc.GradientFillLinear(r, bottom_color, top_color, wxNORTH);

        r.y += r.height;
        r.y--;

        // -- draw bottom fill for glossy look
        top_color = m_baseColour;
        bottom_color = m_baseColour;
        dc.GradientFillLinear(r, top_color, bottom_color, wxSOUTH);
    }

    // draw tab outline
    dc.SetPen(m_borderPen);
    dc.SetBrush(*wxTRANSPARENT_BRUSH);
    dc.DrawPolygon(WXSIZEOF(border_points), border_points);

    // there are two horizontal grey lines at the bottom of the tab control,
    // this gets rid of the top one of those lines in the tab control
    if (page.active)
    {
        if (m_flags & wxAUI_NB_BOTTOM)
            dc.SetPen(wxPen(m_baseColour.ChangeLightness(170)));
        // TODO: else if (m_flags &wxAUI_NB_LEFT) {}
        // TODO: else if (m_flags &wxAUI_NB_RIGHT) {}
        else //for wxAUI_NB_TOP
            dc.SetPen(m_baseColourPen);
        dc.DrawLine(border_points[0].x + 1,
            border_points[0].y,
            border_points[5].x,
            border_points[5].y);
    }


    int text_offset;
    int bitmap_offset = 0;
    if (page.bitmap.IsOk())
    {
        bitmap_offset = tab_x + wnd->FromDIP(8);

        const wxBitmap bitmap = page.bitmap.GetBitmapFor(wnd);

        // draw bitmap
        dc.DrawBitmap(bitmap,
            bitmap_offset,
            drawn_tab_yoff + (drawn_tab_height / 2) - (bitmap.GetLogicalHeight() / 2),
            true);

        text_offset = bitmap_offset + bitmap.GetLogicalWidth();
        text_offset += wnd->FromDIP(3); // bitmap padding
    }
    else
    {
        text_offset = tab_x + wnd->FromDIP(8);
    }

    // draw close button if necessary
    int close_button_width = 0;

    wxString draw_text = caption;

    // 
    size_t pos = 0; size_t poss = 0;
    int x = text_offset;
    int ttx, tty;
    int y = drawn_tab_yoff + (drawn_tab_height) / 2 - (texty / 2) - 1;
    {
        wxDCBrushChanger setBrush(dc, *wxYELLOW_BRUSH);
        while ((pos = draw_text.find(' ', poss)) != wxString::npos) {
            dc.GetTextExtent(draw_text.SubString(poss, pos), &ttx, &tty);
            x += ttx;
            size_t en=draw_text.find(',', pos + 1);
            if (en == wxString::npos) en = draw_text.Len();
            dc.GetTextExtent(draw_text.SubString(pos+1, en-1), &ttx, &tty);
            wxRect r(x, y, ttx, tty);
            dc.DrawRoundedRectangle(r, 2);
            x += ttx;
            poss = en;

        }
        //
    }
    // draw tab text
    wxColor sys_color = wxSystemSettings::GetColour(
        page.active ? wxSYS_COLOUR_CAPTIONTEXT : wxSYS_COLOUR_INACTIVECAPTIONTEXT);
    dc.SetTextForeground(sys_color);
    dc.DrawText(draw_text,
        text_offset,
        drawn_tab_yoff + (drawn_tab_height) / 2 - (texty / 2) - 1);

    // draw focus rectangle except under macOS where it looks out of place
#ifndef __WXOSX__
    if (page.active && (wnd->FindFocus() == wnd))
    {
        wxRect focusRectText(text_offset, (drawn_tab_yoff + (drawn_tab_height) / 2 - (texty / 2) - 1),
            selected_textx, selected_texty);

        wxRect focusRect;
        wxRect focusRectBitmap;

        if (page.bitmap.IsOk())
        {
            const wxBitmap bitmap = page.bitmap.GetBitmapFor(wnd);

            focusRectBitmap = wxRect(bitmap_offset, drawn_tab_yoff + (drawn_tab_height / 2) - (bitmap.GetLogicalHeight() / 2),
                bitmap.GetLogicalWidth(), bitmap.GetLogicalHeight());
        }

        if (page.bitmap.IsOk() && draw_text.IsEmpty())
            focusRect = focusRectBitmap;
        else if (!page.bitmap.IsOk() && !draw_text.IsEmpty())
            focusRect = focusRectText;
        else if (page.bitmap.IsOk() && !draw_text.IsEmpty())
            focusRect = focusRectText.Union(focusRectBitmap);

        focusRect.Inflate(2, 2);

        wxRendererNative::Get().DrawFocusRect(wnd, dc, focusRect, 0);
    }
#endif // !__WXOSX__

    * out_tab_rect = wxRect(tab_x, tab_y, tab_width, tab_height);

    dc.DestroyClippingRegion();

}
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;
    // Setup accelerators
    wxAcceleratorEntry entries[2];
    entries[0].Set(wxACCEL_CTRL, (int)'F', MNU_FIND);
    entries[1].Set(wxACCEL_CTRL, (int)'S', MNU_SEND_MAIL);
    wxAcceleratorTable accel(2, entries);
    SetAcceleratorTable(accel);
    SetAcceleratorTable(accel);

    m_notebook = new wxAuiNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,0 );
    m_notebook->SetFont(settings->GetSystemFont());
    MywxAuiDefaultTabArt* art = new MywxAuiDefaultTabArt();
    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->SetFont(settings->GetSystemFont());
    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_UP, 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);
    //wxObjectDataPtr<StorageModel> 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_EXTENDED_LOG, "Clear table"), border1);
    sSizer->Add(new wxStaticText(testPanel, wxID_ANY, "Load last file counts:"), border1);
    sSizer->Add(new wxTextCtrl(testPanel, ID_COUNT_FILES, "1",wxDefaultPosition,wxDefaultSize, wxTE_PROCESS_ENTER), border1);

    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);
    sSizer->Add(new wxButton(testPanel, ID_HELP_LOG, "Help"), border1);
    smart = new wxComboBox(testPanel, ID_CBOX_SMART, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, 0);
    //smart->SetWindowStyle(smart->GetWindowStyle()|wxCB_READONLY);
    
    smart->SetMinSize(wxSize(300,-1));
    //listUserFilter = new wxComboBox(testPanel, ID_CBOX_UFilter, wxT("Combo!"), wxDefaultPosition, wxDefaultSize, 0, NULL, 0);
    sSizer->Add(smart, border1);
    my_view->smart = smart;

    zeroPanelSz->Add(sSizer);
    testPanel->SetSizerAndFit(zeroPanelSz);

    m_notebook->AddPage(testPanel, "Log");
    wxBoxSizer* setingSZ = new wxBoxSizer(wxHORIZONTAL);
    setingSZ->Add(m_notebook);

    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<wxString> 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());
    bool onlyOne = srv->GetConnected();
    if (onlyOne) {
        SetTitle(_title + wxString::Format("(%s)", srvname));
        lb->Enable(false);
    }
    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())) {
                if (!onlyOne || (onlyOne && srvname == l)) {
                    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(wxHORIZONTAL);
    lb->SetMinSize(wxSize(-1, 200));
    zeroPanelSz2->Add(lb, 1, wxGROW | wxALL, 5);

    wxPanel *m_panel2 = new wxPanel(settingPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);

    zeroPanelSz2->Add(m_panel2, 1, wxEXPAND | wxALL, 5);

    wxBoxSizer* bSizer3;
    bSizer3 = new wxBoxSizer(wxVERTICAL);

    wxBoxSizer* bSizer2;
    bSizer2 = new wxBoxSizer(wxHORIZONTAL);
    listUserFilter = new wxComboBox(m_panel2, ID_CBOX_UFilter, wxT("Combo!"), wxDefaultPosition, wxDefaultSize, 0, NULL, 0);
    bSizer2->Add(listUserFilter, 1, wxALL | wxEXPAND, 2);
    wxButton *m_button1 = new wxButton(m_panel2, ID_ADD_UFilter, wxT("Add"), wxDefaultPosition, wxDefaultSize, 0);
    m_button1->SetMaxSize(wxSize(30, -1));
    bSizer2->Add(m_button1, 0, wxALL, 2);
    wxButton *m_button2 = new wxButton(m_panel2, ID_DEL_UFilter, wxT("Del"), wxDefaultPosition, wxDefaultSize, 0);
    m_button2->SetMaxSize(wxSize(30, -1));
    bSizer2->Add(m_button2, 0, wxALL, 2);
    bSizer3->Add(bSizer2, 0, wxEXPAND, 5);

    wxBoxSizer* bSizer5;
    bSizer5 = new wxBoxSizer(wxVERTICAL);

    contentFilter = new wxTextCtrl(m_panel2, ID_TEXT_UFilter, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
    bSizer5->Add(contentFilter, 1, wxALL | wxEXPAND, 2);
    bSizer3->Add(bSizer5, 1, wxEXPAND, 2);
    m_panel2->SetSizer(bSizer3);

    //listUserFilter->Bind();
    
    settingPanel->SetSizerAndFit(zeroPanelSz2);
    m_notebook->AddPage(settingPanel, "Settings");
    m_notebook->SetArtProvider(art);
    //m_notebook->SetSelectedFont(settings->GetSystemFont());
    //m_notebook->SetNormalFont(settings->GetSystemFont());
    
    bool b=true;
    settings->Read(dlgName + "/Mode",&b, false);
    group->SetValue(b);
    my_view->setGroupMode(b);
    my_view->ModUserFilter("","Init", listUserFilter, contentFilter);
//	if (mainForm) getFilename();
    Connect(wxID_ANY, wxEVT_THREAD, wxThreadEventHandler(frmLog::OnAddLabelTextThread), NULL, this);
    DBthread = new MyThread(conArray,this,1);
    if (DBthread->Create() != wxTHREAD_NO_ERROR)
    {
        wxLogError(wxT("Can’t create thread!"));
    }
    DBthread->Run();
    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()
{
    m_timer.Stop();
    // 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();
    // If the status window wasn't launched in standalone mode...
    if (mainForm)
        mainForm->RemoveFrame(this);

    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;
}




