OSDN Git Service

Merge "Update samples to use new getMotionRanges() API." into honeycomb-mr1
[android-x86/development.git] / simulator / app / LogWindow.cpp
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