Home | History | Annotate | Download | only in app
      1 //
      2 // Copyright 2005 The Android Open Source Project
      3 //
      4 // Display runtime log output.
      5 //
      6 
      7 // For compilers that support precompilation, include "wx/wx.h".
      8 #include "wx/wxprec.h"
      9 
     10 // Otherwise, include all standard headers
     11 #ifndef WX_PRECOMP
     12 # include "wx/wx.h"
     13 #endif
     14 #include "wx/image.h"   // needed for Windows build
     15 #include "wx/dcbuffer.h"
     16 
     17 #include "LogWindow.h"
     18 #include "LogMessage.h"
     19 #include "LogPrefsDialog.h"
     20 #include "MyApp.h"
     21 #include "Preferences.h"
     22 #include "Resource.h"
     23 #include "UserEventMessage.h"
     24 
     25 #include <errno.h>
     26 
     27 static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...);
     28 static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args);
     29 
     30 
     31 using namespace android;
     32 
     33 #if 0   // experiment -- works on Win32, but not with GTK
     34 class MyTextCtrl : public wxTextCtrl {
     35 public:
     36     MyTextCtrl(wxWindow* parent, wxWindowID id, const wxString& value,
     37         const wxPoint& pos, const wxSize& size, int style = 0)
     38         : wxTextCtrl(parent, id, value, pos, size, style)
     39         {
     40             printf("***************** MyTextCtrl!\n");
     41         }
     42 
     43     void OnScroll(wxScrollWinEvent& event);
     44     void OnScrollBottom(wxScrollWinEvent& event);
     45 
     46 private:
     47     DECLARE_EVENT_TABLE()
     48 };
     49 
     50 BEGIN_EVENT_TABLE(MyTextCtrl, wxTextCtrl)
     51     EVT_SCROLLWIN(MyTextCtrl::OnScroll)
     52     EVT_SCROLLWIN_BOTTOM(MyTextCtrl::OnScrollBottom)
     53 END_EVENT_TABLE()
     54 
     55 void MyTextCtrl::OnScroll(wxScrollWinEvent& event)
     56 {
     57     printf("OnScroll!\n");
     58 }
     59 
     60 void MyTextCtrl::OnScrollBottom(wxScrollWinEvent& event)
     61 {
     62     printf("OnScrollBottom!\n");
     63 }
     64 #endif
     65 
     66 
     67 BEGIN_EVENT_TABLE(LogWindow, wxDialog)
     68     EVT_CLOSE(LogWindow::OnClose)
     69     EVT_MOVE(LogWindow::OnMove)
     70     EVT_COMBOBOX(IDC_LOG_LEVEL, LogWindow::OnLogLevel)
     71     EVT_BUTTON(IDC_LOG_CLEAR, LogWindow::OnLogClear)
     72     EVT_BUTTON(IDC_LOG_PAUSE, LogWindow::OnLogPause)
     73     EVT_BUTTON(IDC_LOG_PREFS, LogWindow::OnLogPrefs)
     74 END_EVENT_TABLE()
     75 
     76 /*
     77  * Information about log levels.
     78  *
     79  * Each entry here corresponds to an entry in the combo box.  The first
     80  * letter of each name should be unique.
     81  */
     82 static const struct {
     83     wxString    name;
     84     android_LogPriority priority;
     85 } gLogLevels[] = {
     86     { wxT("Verbose"),    ANDROID_LOG_VERBOSE },
     87     { wxT("Debug"),      ANDROID_LOG_DEBUG },
     88     { wxT("Info"),       ANDROID_LOG_INFO },
     89     { wxT("Warn"),       ANDROID_LOG_WARN },
     90     { wxT("Error"),      ANDROID_LOG_ERROR }
     91 };
     92 
     93 
     94 /*
     95  * Create a new LogWindow.  This should be a child of the main frame.
     96  */
     97 LogWindow::LogWindow(wxWindow* parent)
     98     : wxDialog(parent, wxID_ANY, wxT("Log Output"), wxDefaultPosition,
     99         wxDefaultSize,
    100         wxCAPTION | wxSYSTEM_MENU | wxCLOSE_BOX | wxRESIZE_BORDER),
    101       mDisplayArray(NULL), mMaxDisplayMsgs(0), mPaused(false),
    102       mMinPriority(ANDROID_LOG_VERBOSE),
    103       mHeaderFormat(LogPrefsDialog::kHFFull),
    104       mSingleLine(false), mExtraSpacing(0), mPointSize(10), mUseColor(true),
    105       mFontMonospace(true), mWriteFile(false), mTruncateOld(true), mLogFp(NULL),
    106       mNewlyShown(false), mLastPosition(wxDefaultPosition), mVisible(false)
    107 {
    108     ConstructControls();
    109 
    110     Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    111 
    112     int poolSize = 10240;       // 10MB
    113     pPrefs->GetInt("log-pool-size-kbytes", &poolSize);
    114     assert(poolSize > 0);
    115     mPool.Resize(poolSize * 1024);
    116 
    117     mMaxDisplayMsgs = 1000;
    118     pPrefs->GetInt("log-display-msg-count", &mMaxDisplayMsgs);
    119     assert(mMaxDisplayMsgs > 0);
    120     mDisplayArray = new LogMessage*[mMaxDisplayMsgs];
    121     memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs);
    122     mTopPtr = -1;
    123     mNextPtr = 0;
    124 
    125     int tmpInt = (int) mHeaderFormat;
    126     pPrefs->GetInt("log-header-format", &tmpInt);
    127     mHeaderFormat = (LogPrefsDialog::HeaderFormat) tmpInt;
    128     pPrefs->GetBool("log-single-line", &mSingleLine);
    129     pPrefs->GetInt("log-extra-spacing", &mExtraSpacing);
    130     pPrefs->GetInt("log-point-size", &mPointSize);
    131     pPrefs->GetBool("log-use-color", &mUseColor);
    132     pPrefs->SetBool("log-font-monospace", &mFontMonospace);
    133     SetTextStyle();
    134 
    135     mFileName = wxT("/tmp/android-log.txt");
    136     pPrefs->GetBool("log-write-file", &mWriteFile);
    137     pPrefs->GetString("log-filename", /*ref*/mFileName);
    138     pPrefs->GetBool("log-truncate-old", &mTruncateOld);
    139 
    140     PrepareLogFile();
    141 }
    142 
    143 /*
    144  * Destroy everything we own.
    145  */
    146 LogWindow::~LogWindow(void)
    147 {
    148     ClearDisplay();
    149     delete[] mDisplayArray;
    150 
    151     if (mLogFp != NULL)
    152         fclose(mLogFp);
    153 }
    154 
    155 /*
    156  * Set the text style, based on our preferences.
    157  */
    158 void LogWindow::SetTextStyle(void)
    159 {
    160     wxTextCtrl* pTextCtrl;
    161     pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT);
    162     wxTextAttr style;
    163     style = pTextCtrl->GetDefaultStyle();
    164 
    165     if (mFontMonospace) {
    166         wxFont font(mPointSize, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL,
    167             wxFONTWEIGHT_NORMAL);
    168         style.SetFont(font);
    169     } else {
    170         wxFont font(mPointSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
    171             wxFONTWEIGHT_NORMAL);
    172         style.SetFont(font);
    173     }
    174 
    175     pTextCtrl->SetDefaultStyle(style);
    176 }
    177 
    178 /*
    179  * Set up the goodies in the window.
    180  *
    181  * Also initializes mMinPriority.
    182  */
    183 void LogWindow::ConstructControls(void)
    184 {
    185     Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    186     wxPanel* base = new wxPanel(this, wxID_ANY);
    187     wxBoxSizer* masterSizer = new wxBoxSizer(wxVERTICAL);
    188     wxBoxSizer* indentSizer = new wxBoxSizer(wxHORIZONTAL);
    189     wxBoxSizer* configPrioritySizer = new wxBoxSizer(wxHORIZONTAL);
    190     wxGridSizer* configSizer = new wxGridSizer(4, 1);
    191 
    192     /*
    193      * Configure log level combo box.
    194      */
    195     wxComboBox* logLevel;
    196     int defaultLogLevel = 1;
    197     pPrefs->GetInt("log-display-level", &defaultLogLevel);
    198     logLevel = new wxComboBox(base, IDC_LOG_LEVEL, wxT(""),
    199         wxDefaultPosition, wxDefaultSize, 0, NULL,
    200         wxCB_READONLY /*| wxSUNKEN_BORDER*/);
    201     for (int i = 0; i < NELEM(gLogLevels); i++) {
    202         logLevel->Append(gLogLevels[i].name);
    203         logLevel->SetClientData(i, (void*) gLogLevels[i].priority);
    204     }
    205     logLevel->SetSelection(defaultLogLevel);
    206     mMinPriority = gLogLevels[defaultLogLevel].priority;
    207 
    208     /*
    209      * Set up stuff at the bottom, starting with the options
    210      * at the bottom left.
    211      */
    212     configPrioritySizer->Add(new wxStaticText(base, wxID_ANY, wxT("Log level:"),
    213             wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT),
    214         0, wxALIGN_CENTER_VERTICAL);
    215     configPrioritySizer->AddSpacer(kInterSpacing);
    216     configPrioritySizer->Add(logLevel);
    217 
    218     wxButton* clear = new wxButton(base, IDC_LOG_CLEAR, wxT("&Clear"),
    219         wxDefaultPosition, wxDefaultSize, 0);
    220     wxButton* pause = new wxButton(base, IDC_LOG_PAUSE, wxT("&Pause"),
    221         wxDefaultPosition, wxDefaultSize, 0);
    222     wxButton* prefs = new wxButton(base, IDC_LOG_PREFS, wxT("C&onfigure"),
    223         wxDefaultPosition, wxDefaultSize, 0);
    224 
    225     configSizer->Add(configPrioritySizer, 0, wxALIGN_LEFT);
    226     configSizer->Add(clear, 0, wxALIGN_CENTER);
    227     configSizer->Add(pause, 0, wxALIGN_CENTER);
    228     configSizer->Add(prefs, 0, wxALIGN_RIGHT);
    229 
    230     /*
    231      * Create text ctrl.
    232      */
    233     wxTextCtrl* pTextCtrl;
    234     pTextCtrl = new wxTextCtrl(base, IDC_LOG_TEXT, wxT(""),
    235         wxDefaultPosition, wxDefaultSize,
    236         wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxTE_NOHIDESEL |
    237             wxHSCROLL);
    238 
    239     /*
    240      * Add components to master sizer.
    241      */
    242     masterSizer->AddSpacer(kEdgeSpacing);
    243     masterSizer->Add(pTextCtrl, 1, wxEXPAND);
    244     masterSizer->AddSpacer(kInterSpacing);
    245     masterSizer->Add(configSizer, 0, wxEXPAND);
    246     masterSizer->AddSpacer(kEdgeSpacing);
    247 
    248     /*
    249      * Indent from sides.
    250      */
    251     indentSizer->AddSpacer(kEdgeSpacing);
    252     indentSizer->Add(masterSizer, 1, wxEXPAND);
    253     indentSizer->AddSpacer(kEdgeSpacing);
    254 
    255     base->SetSizer(indentSizer);
    256 
    257     indentSizer->Fit(this);             // shrink-to-fit
    258     indentSizer->SetSizeHints(this);    // define minimum size
    259 }
    260 
    261 /*
    262  * In some cases, this means the user has clicked on our "close" button.
    263  * We don't really even want one, but both WinXP and KDE put one on our
    264  * window whether we want it or not.  So, we make it work as a "hide"
    265  * button instead.
    266  *
    267  * This also gets called when the app is shutting down, and we do want
    268  * to destroy ourselves then, saving various information about our state.
    269  */
    270 void LogWindow::OnClose(wxCloseEvent& event)
    271 {
    272     /* just hide the window, unless we're shutting down */
    273     if (event.CanVeto()) {
    274         event.Veto();
    275         Show(false);
    276         return;
    277     }
    278 
    279     /*
    280      * Save some preferences.
    281      */
    282     SaveWindowPrefs();
    283 
    284     /* if we can't veto the Close(), destroy ourselves */
    285     Destroy();
    286 }
    287 
    288 /*
    289  * Save all of our preferences to the config file.
    290  */
    291 void LogWindow::SaveWindowPrefs(void)
    292 {
    293     Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    294 
    295     /*
    296      * Save shown/hidden state.
    297      */
    298     pPrefs->SetBool("window-log-show", IsShown());
    299 
    300     /*
    301      * Limits and formatting prefs.
    302      */
    303     pPrefs->SetInt("log-display-msg-count", mMaxDisplayMsgs);
    304     pPrefs->SetInt("log-pool-size-kbytes", mPool.GetMaxSize() / 1024);
    305 
    306     pPrefs->SetInt("log-header-format", mHeaderFormat);
    307     pPrefs->SetBool("log-single-line", mSingleLine);
    308     pPrefs->SetInt("log-extra-spacing", mExtraSpacing);
    309     pPrefs->SetInt("log-point-size", mPointSize);
    310     pPrefs->SetBool("log-use-color", mUseColor);
    311     pPrefs->SetBool("log-font-monospace", mFontMonospace);
    312 
    313     pPrefs->SetBool("log-write-file", mWriteFile);
    314     pPrefs->SetString("log-filename", mFileName.ToAscii());
    315     pPrefs->SetBool("log-truncate-old", mTruncateOld);
    316 
    317     /*
    318      * Save window size and position.
    319      */
    320     wxPoint posn;
    321     wxSize size;
    322 
    323     assert(pPrefs != NULL);
    324 
    325     posn = GetPosition();
    326     size = GetSize();
    327 
    328     pPrefs->SetInt("window-log-x", posn.x);
    329     pPrefs->SetInt("window-log-y", posn.y);
    330     pPrefs->SetInt("window-log-width", size.GetWidth());
    331     pPrefs->SetInt("window-log-height", size.GetHeight());
    332 
    333     /*
    334      * Save current setting of debug level combo box.
    335      */
    336     wxComboBox* pCombo;
    337     int selection;
    338     pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL);
    339     selection = pCombo->GetSelection();
    340     pPrefs->SetInt("log-display-level", selection);
    341 }
    342 
    343 /*
    344  * Return the desired position and size.
    345  */
    346 /*static*/ wxRect LogWindow::GetPrefWindowRect(void)
    347 {
    348     Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    349     int x, y, width, height;
    350 
    351     assert(pPrefs != NULL);
    352 
    353     x = y = 10;
    354     width = 500;
    355     height = 200;
    356 
    357     /* these don't modify the arg if the pref doesn't exist */
    358     pPrefs->GetInt("window-log-x", &x);
    359     pPrefs->GetInt("window-log-y", &y);
    360     pPrefs->GetInt("window-log-width", &width);
    361     pPrefs->GetInt("window-log-height", &height);
    362 
    363     return wxRect(x, y, width, height);
    364 }
    365 
    366 /*
    367  * Under Linux+GTK, the first time you show the window, it appears where
    368  * it's supposed to.  If you then hide it and show it again, it gets
    369  * moved on top of the parent window.  After that, you can reposition it
    370  * and it remembers its position across hide/show.
    371  *
    372  * To counter this annoyance, we save the position when we hide, and
    373  * reset the position after a show.  The "newly shown" flag ensures that
    374  * we only reposition the window as the result of a Show(true) call.
    375  *
    376  * Sometimes, something helpful will shift the window over if it's
    377  * partially straddling a seam between two monitors.  I don't see an easy
    378  * way to block this, and I'm not sure I want to anyway.
    379  */
    380 void LogWindow::OnMove(wxMoveEvent& event)
    381 {
    382     wxPoint point;
    383     point = event.GetPosition();
    384     //printf("Sim: log window is at (%d,%d) (new=%d)\n", point.x, point.y,
    385     //    mNewlyShown);
    386 
    387     if (mNewlyShown) {
    388         if (mLastPosition == wxDefaultPosition) {
    389             //printf("Sim: no last position established\n");
    390         } else {
    391             Move(mLastPosition);
    392         }
    393 
    394         mNewlyShown = false;
    395     }
    396 }
    397 
    398 /*
    399  * Set the "newly shown" flag.
    400  */
    401 bool LogWindow::Show(bool show)
    402 {
    403     if (show) {
    404         mNewlyShown = true;
    405         Redisplay();
    406     } else {
    407         mLastPosition = GetPosition();
    408     }
    409 
    410     mVisible = show;
    411     return wxDialog::Show(show);
    412 }
    413 
    414 /*
    415  * User has adjusted the log level.  Update the display appropriately.
    416  *
    417  * This is a wxEVT_COMMAND_COMBOBOX_SELECTED event.
    418  */
    419 void LogWindow::OnLogLevel(wxCommandEvent& event)
    420 {
    421     int selection;
    422     android_LogPriority priority;
    423 
    424     selection = event.GetInt();
    425     wxComboBox* pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL);
    426     priority = (android_LogPriority) (long)pCombo->GetClientData(event.GetInt());
    427 
    428     printf("Sim: log level selected: %d (%s)\n", (int) priority,
    429         (const char*) gLogLevels[selection].name.ToAscii());
    430     mMinPriority = priority;
    431     Redisplay();
    432 }
    433 
    434 /*
    435  * Clear out the log.
    436  */
    437 void LogWindow::OnLogClear(wxCommandEvent& event)
    438 {
    439     ClearDisplay();
    440     mPool.Clear();
    441 }
    442 
    443 /*
    444  * Handle the pause/resume button.
    445  *
    446  * If we're un-pausing, we need to get caught up.
    447  */
    448 void LogWindow::OnLogPause(wxCommandEvent& event)
    449 {
    450     mPaused = !mPaused;
    451 
    452     wxButton* pButton = (wxButton*) FindWindow(IDC_LOG_PAUSE);
    453     if (mPaused) {
    454         pButton->SetLabel(wxT("&Resume"));
    455 
    456         mPool.SetBookmark();
    457     } else {
    458         pButton->SetLabel(wxT("&Pause"));
    459 
    460         LogMessage* pMsg = mPool.GetBookmark();
    461         if (pMsg == NULL) {
    462             /* bookmarked item fell out of pool */
    463             printf("--- bookmark was lost, redisplaying\n");
    464             Redisplay();
    465         } else {
    466             /*
    467              * The bookmark points to the last item added to the display.
    468              * We want to chase its "prev" pointer to walk toward the head
    469              * of the list, adding items from oldest to newest.
    470              */
    471             pMsg = pMsg->GetPrev();
    472             while (pMsg != NULL) {
    473                 if (FilterMatches(pMsg))
    474                     AddToDisplay(pMsg);
    475                 pMsg = pMsg->GetPrev();
    476             }
    477         }
    478     }
    479 }
    480 
    481 /*
    482  * Open log preferences dialog.
    483  */
    484 void LogWindow::OnLogPrefs(wxCommandEvent& event)
    485 {
    486     LogPrefsDialog dialog(this);
    487 
    488     /*
    489      * Set up the dialog.
    490      */
    491     dialog.mHeaderFormat = mHeaderFormat;
    492     dialog.mSingleLine = mSingleLine;
    493     dialog.mExtraSpacing = mExtraSpacing;
    494     dialog.mPointSize = mPointSize;
    495     dialog.mUseColor = mUseColor;
    496     dialog.mFontMonospace = mFontMonospace;
    497 
    498     dialog.mDisplayMax = mMaxDisplayMsgs;
    499     dialog.mPoolSizeKB = mPool.GetMaxSize() / 1024;
    500 
    501     dialog.mWriteFile = mWriteFile;
    502     dialog.mFileName = mFileName;
    503     dialog.mTruncateOld = mTruncateOld;
    504 
    505     /*
    506      * Show it.  If they hit "OK", copy the updated values out, and
    507      * re-display the log output.
    508      */
    509     if (dialog.ShowModal() == wxID_OK) {
    510         /* discard old display arra */
    511         ClearDisplay();
    512         delete[] mDisplayArray;
    513 
    514         mHeaderFormat = dialog.mHeaderFormat;
    515         mSingleLine = dialog.mSingleLine;
    516         mExtraSpacing = dialog.mExtraSpacing;
    517         mPointSize = dialog.mPointSize;
    518         mUseColor = dialog.mUseColor;
    519         mFontMonospace = dialog.mFontMonospace;
    520 
    521         assert(dialog.mDisplayMax > 0);
    522         assert(dialog.mPoolSizeKB > 0);
    523         mMaxDisplayMsgs = dialog.mDisplayMax;
    524         mPool.Resize(dialog.mPoolSizeKB * 1024);
    525 
    526         mWriteFile = dialog.mWriteFile;
    527         if (mLogFp != NULL && mFileName != dialog.mFileName) {
    528             printf("--- log file name changed, closing\n");
    529             fclose(mLogFp);
    530             mLogFp = NULL;
    531         }
    532         mFileName = dialog.mFileName;
    533         mTruncateOld = dialog.mTruncateOld;
    534 
    535         mDisplayArray = new LogMessage*[mMaxDisplayMsgs];
    536         memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs);
    537         Redisplay();
    538 
    539         PrepareLogFile();
    540     }
    541 }
    542 
    543 /*
    544  * Handle a log message "user event".  This should only be called in
    545  * the main UI thread.
    546  *
    547  * We take ownership of "*pLogMessage".
    548  */
    549 void LogWindow::AddLogMessage(LogMessage* pLogMessage)
    550 {
    551     mPool.Add(pLogMessage);
    552 
    553     if (!mPaused && mVisible && FilterMatches(pLogMessage)) {
    554         /*
    555          * Thought: keep a reference to the previous message.  If it
    556          * matches in most fields (all except timestamp?), hold it and
    557          * increment a counter.  If we get a message that doesn't match,
    558          * or a timer elapses, synthesize a "previous message repeated N
    559          * times" string.
    560          */
    561         AddToDisplay(pLogMessage);
    562     }
    563 
    564     // release the initial ref caused by allocation
    565     pLogMessage->Release();
    566 
    567     if (mLogFp != NULL)
    568         LogToFile(pLogMessage);
    569 }
    570 
    571 /*
    572  * Clear out the display, releasing any log messages held in the display
    573  * array.
    574  */
    575 void LogWindow::ClearDisplay(void)
    576 {
    577     wxTextCtrl* pTextCtrl;
    578     pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT);
    579     pTextCtrl->Clear();
    580 
    581     /*
    582      * Just run through the entire array.
    583      */
    584     for (int i = 0; i < mMaxDisplayMsgs; i++) {
    585         if (mDisplayArray[i] != NULL) {
    586             mDisplayArray[i]->Release();
    587             mDisplayArray[i] = NULL;
    588         }
    589     }
    590     mTopPtr = -1;
    591     mNextPtr = 0;
    592 }
    593 
    594 /*
    595  * Clear the current display and regenerate it from the log pool.  We need
    596  * to do this whenever we change filters or log message formatting.
    597  */
    598 void LogWindow::Redisplay(void)
    599 {
    600     /*
    601      * Freeze output rendering so it doesn't flash during update.  Doesn't
    602      * seem to help for GTK, and it leaves garbage on the screen in WinXP,
    603      * so I'm leaving it commented out.
    604      */
    605     //wxTextCtrl* pText = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT);
    606     //pText->Freeze();
    607 
    608     //printf("--- redisplay\n");
    609     ClearDisplay();
    610 
    611     /*
    612      * Set up the default wxWidgets text style stuff.
    613      */
    614     SetTextStyle();
    615 
    616     /*
    617      * Here's the plan:
    618      * - Start at the head of the pool (where the most recently added
    619      *   items are).
    620      * - Check to see if the current item passes our filter.  If it does,
    621      *   increment the "found count".
    622      * - Continue in this manner until we run out of pool or have
    623      *   sufficient items to fill the screen.
    624      * - Starting from the current position, walk back toward the head,
    625      *   adding the items that meet the current filter criteria.
    626      *
    627      * Don't forget that the log pool could be empty.
    628      */
    629     LogMessage* pMsg = mPool.GetHead();
    630 
    631     if (pMsg != NULL) {
    632         int foundCount = 0;
    633 
    634         // note this stops before it runs off the end
    635         while (pMsg->GetNext() != NULL && foundCount < mMaxDisplayMsgs) {
    636             if (FilterMatches(pMsg))
    637                 foundCount++;
    638             pMsg = pMsg->GetNext();
    639         }
    640 
    641         while (pMsg != NULL) {
    642             if (FilterMatches(pMsg))
    643                 AddToDisplay(pMsg);
    644             pMsg = pMsg->GetPrev();
    645         }
    646     }
    647 
    648     //pText->Thaw();
    649 }
    650 
    651 
    652 /*
    653  * Returns "true" if the currently specified filters would allow this
    654  * message to be shown.
    655  */
    656 bool LogWindow::FilterMatches(const LogMessage* pLogMessage)
    657 {
    658     if (pLogMessage->GetPriority() >= mMinPriority)
    659         return true;
    660     else
    661         return false;
    662 }
    663 
    664 /*
    665  * Realloc the array of pointers, and remove anything from the display
    666  * that should no longer be there.
    667  */
    668 void LogWindow::SetMaxDisplayMsgs(int max)
    669 {
    670     Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    671 
    672     pPrefs->SetInt("log-display-msg-count", max);
    673 }
    674 
    675 /*
    676  * Add the message to the display array and to the screen.
    677  */
    678 void LogWindow::AddToDisplay(LogMessage* pLogMessage)
    679 {
    680     wxTextCtrl* pTextCtrl;
    681     pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT);
    682 
    683     if (mNextPtr == mTopPtr) {
    684         /*
    685          * The display array is full.
    686          *
    687          * We need to eliminate the topmost entry.  This requires removing
    688          * it from the array and removing the text from the wxTextCtrl.
    689          */
    690         pTextCtrl->Remove(0, mDisplayArray[mTopPtr]->GetTextCtrlLen());
    691         mDisplayArray[mTopPtr]->Release();
    692         mTopPtr = (mTopPtr + 1) % mMaxDisplayMsgs;
    693     }
    694 
    695     /*
    696      * Add formatted text to the text ctrl.  Track how much actual space
    697      * is required.  The space may be different on Win32 (CRLF-based) vs.
    698      * GTK (LF-based), so we need to measure it, not compute it from the
    699      * text string.
    700      */
    701     long lastBefore, lastAfter;
    702     //long insertBefore;
    703     //insertBefore = pTextCtrl->GetInsertionPoint();
    704     lastBefore = pTextCtrl->GetLastPosition();
    705     FormatMessage(pLogMessage, pTextCtrl);
    706     lastAfter = pTextCtrl->GetLastPosition();
    707     pLogMessage->SetTextCtrlLen(lastAfter - lastBefore);
    708 
    709     /*
    710      * If we restore the old insertion point, we will be glued to where
    711      * we were.  This is okay until we start deleting text from the top,
    712      * at which point we need to adjust it to retain our position.
    713      *
    714      * If we set the insertion point to the bottom, we effectively
    715      * implement "scroll to bottom on output".
    716      *
    717      * If we don't set it at all, we get slightly strange behavior out
    718      * of GTK, which seems to be par for the course here.
    719      */
    720     //pTextCtrl->SetInsertionPoint(insertBefore);     // restore insertion pt
    721     pTextCtrl->SetInsertionPoint(lastAfter);
    722 
    723     /* add it to array, claim ownership */
    724     mDisplayArray[mNextPtr] = pLogMessage;
    725     pLogMessage->Acquire();
    726 
    727     /* adjust pointers */
    728     if (mTopPtr < 0)        // first time only
    729         mTopPtr = 0;
    730     mNextPtr = (mNextPtr + 1) % mMaxDisplayMsgs;
    731 }
    732 
    733 
    734 /*
    735  * Return a human-readable string for the priority level.  Always returns
    736  * a valid string.
    737  */
    738 static const wxCharBuffer GetPriorityString(android_LogPriority priority)
    739 {
    740     int idx;
    741 
    742     idx = (int) priority - (int) ANDROID_LOG_VERBOSE;
    743     if (idx < 0 || idx >= NELEM(gLogLevels))
    744         return "?unknown?";
    745     return gLogLevels[idx].name.ToAscii();
    746 }
    747 
    748 /*
    749  * Format a message and write it to the text control.
    750  */
    751 void LogWindow::FormatMessage(const LogMessage* pLogMessage,
    752     wxTextCtrl* pTextCtrl)
    753 {
    754 #if defined(HAVE_LOCALTIME_R)
    755     struct tm tmBuf;
    756 #endif
    757     struct tm* ptm;
    758     char timeBuf[32];
    759     char msgBuf[256];
    760     int msgLen = 0;
    761     char* outBuf;
    762     char priChar;
    763     LogPrefsDialog::HeaderFormat headerFmt;
    764 
    765     headerFmt = mHeaderFormat;
    766     if (pLogMessage->GetInternal())
    767         headerFmt = LogPrefsDialog::kHFInternal;
    768 
    769     priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0];
    770 
    771     /*
    772      * Get the current date/time in pretty form
    773      *
    774      * It's often useful when examining a log with "less" to jump to
    775      * a specific point in the file by searching for the date/time stamp.
    776      * For this reason it's very annoying to have regexp meta characters
    777      * in the time stamp.  Don't use forward slashes, parenthesis,
    778      * brackets, asterisks, or other special chars here.
    779      */
    780     time_t when = pLogMessage->GetWhen();
    781     const char* fmt = NULL;
    782 #if defined(HAVE_LOCALTIME_R)
    783     ptm = localtime_r(&when, &tmBuf);
    784 #else
    785     ptm = localtime(&when);
    786 #endif
    787     switch (headerFmt) {
    788     case LogPrefsDialog::kHFFull:
    789     case LogPrefsDialog::kHFInternal:
    790         fmt = "%m-%d %H:%M:%S";
    791         break;
    792     case LogPrefsDialog::kHFBrief:
    793     case LogPrefsDialog::kHFMinimal:
    794         fmt = "%H:%M:%S";
    795         break;
    796     default:
    797         break;
    798     }
    799     if (fmt != NULL)
    800         strftime(timeBuf, sizeof(timeBuf), fmt, ptm);
    801     else
    802         strcpy(timeBuf, "-");
    803 
    804     const int kMaxExtraNewlines = 2;
    805     char hdrNewline[2];
    806     char finalNewlines[kMaxExtraNewlines+1 +1];
    807 
    808     if (mSingleLine)
    809         hdrNewline[0] = ' ';
    810     else
    811         hdrNewline[0] = '\n';
    812     hdrNewline[1] = '\0';
    813 
    814     assert(mExtraSpacing <= kMaxExtraNewlines);
    815     int i;
    816     for (i = 0; i < mExtraSpacing+1; i++)
    817         finalNewlines[i] = '\n';
    818     finalNewlines[i] = '\0';
    819 
    820     wxTextAttr msgColor;
    821     switch (pLogMessage->GetPriority()) {
    822     case ANDROID_LOG_WARN:
    823         msgColor.SetTextColour(*wxBLUE);
    824         break;
    825     case ANDROID_LOG_ERROR:
    826         msgColor.SetTextColour(*wxRED);
    827         break;
    828     case ANDROID_LOG_VERBOSE:
    829     case ANDROID_LOG_DEBUG:
    830     case ANDROID_LOG_INFO:
    831     default:
    832         msgColor.SetTextColour(*wxBLACK);
    833         break;
    834     }
    835     if (pLogMessage->GetInternal())
    836         msgColor.SetTextColour(*wxGREEN);
    837 
    838     /*
    839      * Construct a buffer containing the log header.
    840      */
    841     bool splitHeader = true;
    842     outBuf = msgBuf;
    843     switch (headerFmt) {
    844     case LogPrefsDialog::kHFFull:
    845         splitHeader = true;
    846         msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
    847                     "[ %s %5d %c/%-6.6s]%s",
    848                     timeBuf, pLogMessage->GetPid(), priChar,
    849                     pLogMessage->GetTag(), hdrNewline);
    850         break;
    851     case LogPrefsDialog::kHFBrief:
    852         splitHeader = true;
    853         msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
    854                     "[%s %5d]%s",
    855                     timeBuf, pLogMessage->GetPid(), hdrNewline);
    856         break;
    857     case LogPrefsDialog::kHFMinimal:
    858         splitHeader = false;
    859         msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
    860                     "%s %5d- %s",
    861                     timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg());
    862         break;
    863     case LogPrefsDialog::kHFInternal:
    864         splitHeader = false;
    865         msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
    866                     "[%s] %s", timeBuf, pLogMessage->GetMsg());
    867         break;
    868     default:
    869         fprintf(stderr, "Sim: unexpected header format %d\n", headerFmt);
    870         assert(false);
    871         break;
    872     }
    873 
    874     if (msgLen < 0) {
    875         fprintf(stderr, "WHOOPS\n");
    876         assert(outBuf == msgBuf);
    877         return;
    878     }
    879 
    880     if (splitHeader) {
    881         if (mUseColor)
    882             pTextCtrl->SetDefaultStyle(wxTextAttr(*wxLIGHT_GREY));
    883         pTextCtrl->AppendText(wxString::FromAscii(outBuf));
    884         if (mUseColor)
    885             pTextCtrl->SetDefaultStyle(msgColor);
    886         pTextCtrl->AppendText(wxString::FromAscii(pLogMessage->GetMsg()));
    887         if (mUseColor)
    888             pTextCtrl->SetDefaultStyle(*wxBLACK);
    889         pTextCtrl->AppendText(wxString::FromAscii(finalNewlines));
    890     } else {
    891         if (mUseColor)
    892             pTextCtrl->SetDefaultStyle(msgColor);
    893         pTextCtrl->AppendText(wxString::FromAscii(outBuf));
    894         if (mUseColor)
    895             pTextCtrl->SetDefaultStyle(*wxBLACK);
    896         pTextCtrl->AppendText(wxString::FromAscii(finalNewlines));
    897     }
    898 
    899     /* if we allocated storage for this message, free it */
    900     if (outBuf != msgBuf)
    901         free(outBuf);
    902 }
    903 
    904 /*
    905  * Write the message to the log file.
    906  *
    907  * We can't just do this in FormatMessage(), because that re-writes all
    908  * messages on the display whenever the output format or filter changes.
    909  *
    910  * Use a one-log-per-line format here to make "grep" useful.
    911  */
    912 void LogWindow::LogToFile(const LogMessage* pLogMessage)
    913 {
    914 #if defined(HAVE_LOCALTIME_R)
    915     struct tm tmBuf;
    916 #endif
    917     struct tm* ptm;
    918     char timeBuf[32];
    919     char msgBuf[256];
    920     int msgLen;
    921     char* outBuf;
    922     char priChar;
    923 
    924     assert(mLogFp != NULL);
    925 
    926     time_t when = pLogMessage->GetWhen();
    927 #if defined(HAVE_LOCALTIME_R)
    928     ptm = localtime_r(&when, &tmBuf);
    929 #else
    930     ptm = localtime(&when);
    931 #endif
    932 
    933     strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
    934     priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0];
    935 
    936     outBuf = msgBuf;
    937     if (pLogMessage->GetInternal()) {
    938         msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
    939                     "[%s %5d *] %s\n",
    940                     timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg());
    941     } else {
    942         msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
    943                     "[%s %5d %c] %s)\n",
    944                     timeBuf, pLogMessage->GetPid(), priChar,
    945                     pLogMessage->GetMsg());
    946     }
    947     if (fwrite(outBuf, msgLen, 1, mLogFp) != 1)
    948         fprintf(stderr, "Sim: WARNING: partial log write\n");
    949     fflush(mLogFp);
    950 
    951     /* if we allocated storage for this message, free it */
    952     if (outBuf != msgBuf)
    953         free(outBuf);
    954 }
    955 
    956 /*
    957  * Get the modification date of a file.
    958  */
    959 static bool GetFileModDate(const char* fileName, time_t* pModWhen)
    960 {
    961     struct stat sb;
    962 
    963     if (stat(fileName, &sb) < 0)
    964         return false;
    965 
    966     *pModWhen = sb.st_mtime;
    967     return true;
    968 }
    969 
    970 /*
    971  * Open or close the log file as appropriate.
    972  */
    973 void LogWindow::PrepareLogFile(void)
    974 {
    975     const int kLogFileMaxAge = 8 * 60 * 60;     // 8 hours
    976 
    977     if (!mWriteFile && mLogFp != NULL) {
    978         printf("Sim: closing log file\n");
    979         fclose(mLogFp);
    980         mLogFp = NULL;
    981     } else if (mWriteFile && mLogFp == NULL) {
    982         printf("Sim: opening log file '%s'\n", (const char*)mFileName.ToAscii());
    983         time_t now, modWhen = 0;
    984         const char* openFlags;
    985 
    986         now = time(NULL);
    987         if (!mTruncateOld ||
    988             (GetFileModDate(mFileName.ToAscii(), &modWhen) &&
    989              modWhen + kLogFileMaxAge > now))
    990         {
    991             if (modWhen != 0) {
    992                 printf("--- log file is %.3f hours old, appending\n",
    993                     (now - modWhen) / 3600.0);
    994             }
    995             openFlags = "a";        // open for append (text mode)
    996         } else {
    997             if (modWhen != 0) {
    998                 printf("--- log file is %.3f hours old, truncating\n",
    999                     (now - modWhen) / 3600.0);
   1000             }
   1001             openFlags = "w";        // open for writing, truncate (text mode)
   1002         }
   1003 
   1004         mLogFp = fopen(mFileName.ToAscii(), openFlags);
   1005         if (mLogFp == NULL) {
   1006             fprintf(stderr, "Sim: failed opening log file '%s': %s\n",
   1007                 (const char*) mFileName.ToAscii(), strerror(errno));
   1008         } else {
   1009             fprintf(mLogFp, "\n\n");
   1010             fflush(mLogFp);
   1011         }
   1012     }
   1013 }
   1014 
   1015 /*
   1016  * Add a new log message.
   1017  *
   1018  * This function can be called from any thread.  It makes a copy of the
   1019  * stuff in "*pBundle" and sends it to the main UI thread.
   1020  */
   1021 /*static*/ void LogWindow::PostLogMsg(const android_LogBundle* pBundle)
   1022 {
   1023     LogMessage* pNewMessage = LogMessage::Create(pBundle);
   1024 
   1025     SendToWindow(pNewMessage);
   1026 }
   1027 
   1028 /*
   1029  * Post a simple string to the log.
   1030  */
   1031 /*static*/ void LogWindow::PostLogMsg(const char* msg)
   1032 {
   1033     LogMessage* pNewMessage = LogMessage::Create(msg);
   1034 
   1035     SendToWindow(pNewMessage);
   1036 }
   1037 
   1038 /*
   1039  * Post a simple wxString to the log.
   1040  */
   1041 /*static*/ void LogWindow::PostLogMsg(const wxString& msg)
   1042 {
   1043     LogMessage* pNewMessage = LogMessage::Create(msg.ToAscii());
   1044 
   1045     SendToWindow(pNewMessage);
   1046 }
   1047 
   1048 /*
   1049  * Send a log message to the log window.
   1050  */
   1051 /*static*/ void LogWindow::SendToWindow(LogMessage* pMessage)
   1052 {
   1053     if (pMessage != NULL) {
   1054         wxWindow* pMainFrame = ((MyApp*)wxTheApp)->GetMainFrame();
   1055         UserEventMessage* pUem = new UserEventMessage;
   1056         pUem->CreateLogMessage(pMessage);
   1057 
   1058         UserEvent uev(0, (void*) pUem);
   1059 
   1060         pMainFrame->AddPendingEvent(uev);
   1061     } else {
   1062         fprintf(stderr, "Sim: failed to add new log message\n");
   1063     }
   1064 }
   1065 
   1066 
   1067 /*
   1068  * This is a sanity check.  We need to stop somewhere to avoid trashing
   1069  * the system on bad input.
   1070  */
   1071 #define kMaxLen 65536
   1072 
   1073 #define VSNPRINTF vsnprintf     // used to worry about _vsnprintf
   1074 
   1075 
   1076 /*
   1077  * Print a formatted message into a buffer.  Pass in a buffer to try to use.
   1078  *
   1079  * If the buffer isn't big enough to hold the message, allocate storage
   1080  * with malloc() and return that instead.  The caller is responsible for
   1081  * freeing the storage.
   1082  *
   1083  * Returns the length of the string, or -1 if the printf call failed.
   1084  */
   1085 static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args)
   1086 {
   1087     int charsOut;
   1088     char* localBuf = NULL;
   1089 
   1090     assert(pBuf != NULL && *pBuf != NULL);
   1091     assert(bufLen > 0);
   1092     assert(format != NULL);
   1093 
   1094     while (1) {
   1095         /*
   1096          * In some versions of libc, vsnprintf only returns 0 or -1, where
   1097          * -1 indicates the the buffer wasn't big enough.  In glibc 2.1
   1098          * and later, it returns the actual size needed.
   1099          *
   1100          * MinGW is just returning -1, so we have to retry there.
   1101          */
   1102         char* newBuf;
   1103 
   1104         charsOut = VSNPRINTF(*pBuf, bufLen, format, args);
   1105 
   1106         if (charsOut >= 0 && charsOut < bufLen)
   1107             break;
   1108 
   1109         //fprintf(stderr, "EXCEED: %d vs %d\n", charsOut, bufLen);
   1110         if (charsOut < 0) {
   1111             /* exact size not known, double previous size */
   1112             bufLen *= 2;
   1113             if (bufLen > kMaxLen)
   1114                 goto fail;
   1115         } else {
   1116             /* exact size known, just use that */
   1117 
   1118             bufLen = charsOut + 1;
   1119         }
   1120         //fprintf(stderr, "RETRY at %d\n", bufLen);
   1121 
   1122         newBuf = (char*) realloc(localBuf, bufLen);
   1123         if (newBuf == NULL)
   1124             goto fail;
   1125         *pBuf = localBuf = newBuf;
   1126     }
   1127 
   1128     // On platforms where snprintf() doesn't return the number of
   1129     // characters output, we would need to call strlen() here.
   1130 
   1131     return charsOut;
   1132 
   1133 fail:
   1134     if (localBuf != NULL) {
   1135         free(localBuf);
   1136         *pBuf = NULL;
   1137     }
   1138     return -1;
   1139 }
   1140 
   1141 /*
   1142  * Variable-arg form of the above.
   1143  */
   1144 static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...)
   1145 {
   1146     va_list args;
   1147     int result;
   1148 
   1149     va_start(args, format);
   1150     result = android_vsnprintfBuffer(pBuf, bufLen, format, args);
   1151     va_end(args);
   1152 
   1153     return result;
   1154 }
   1155 
   1156 
   1157