1 /*
2 * Copyright 2011-2016, Rene Gollent, rene@gollent.com. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6 #include "InspectorWindow.h"
7
8 #include <stdio.h>
9
10 #include <Alert.h>
11 #include <Application.h>
12 #include <AutoLocker.h>
13 #include <Button.h>
14 #include <ControlLook.h>
15 #include <LayoutBuilder.h>
16 #include <ScrollView.h>
17 #include <StringView.h>
18 #include <TextControl.h>
19
20 #include "AppMessageCodes.h"
21 #include "Architecture.h"
22 #include "CppLanguage.h"
23 #include "GuiTeamUiSettings.h"
24 #include "MemoryView.h"
25 #include "MessageCodes.h"
26 #include "Team.h"
27 #include "UserInterface.h"
28 #include "Value.h"
29
30
31 enum {
32 MSG_NAVIGATE_PREVIOUS_BLOCK = 'npbl',
33 MSG_NAVIGATE_NEXT_BLOCK = 'npnl',
34 MSG_MEMORY_BLOCK_RETRIEVED = 'mbre',
35 MSG_EDIT_CURRENT_BLOCK = 'mecb',
36 MSG_COMMIT_MODIFIED_BLOCK = 'mcmb',
37 MSG_REVERT_MODIFIED_BLOCK = 'mrmb'
38 };
39
40
InspectorWindow(::Team * team,UserInterfaceListener * listener,BHandler * target)41 InspectorWindow::InspectorWindow(::Team* team, UserInterfaceListener* listener,
42 BHandler* target)
43 :
44 BWindow(BRect(100, 100, 700, 500), "Inspector", B_TITLED_WINDOW,
45 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
46 fListener(listener),
47 fAddressInput(NULL),
48 fHexMode(NULL),
49 fTextMode(NULL),
50 fWritableBlockIndicator(NULL),
51 fMemoryView(NULL),
52 fCurrentBlock(NULL),
53 fCurrentAddress(0LL),
54 fTeam(team),
55 fLanguage(NULL),
56 fExpressionInfo(NULL),
57 fTarget(target)
58 {
59 AutoLocker< ::Team> teamLocker(fTeam);
60 fTeam->AddListener(this);
61 }
62
63
~InspectorWindow()64 InspectorWindow::~InspectorWindow()
65 {
66 _SetCurrentBlock(NULL);
67
68 if (fLanguage != NULL)
69 fLanguage->ReleaseReference();
70
71 if (fExpressionInfo != NULL) {
72 fExpressionInfo->RemoveListener(this);
73 fExpressionInfo->ReleaseReference();
74 }
75
76 AutoLocker< ::Team> teamLocker(fTeam);
77 fTeam->RemoveListener(this);
78 }
79
80
81 /* static */ InspectorWindow*
Create(::Team * team,UserInterfaceListener * listener,BHandler * target)82 InspectorWindow::Create(::Team* team, UserInterfaceListener* listener,
83 BHandler* target)
84 {
85 InspectorWindow* self = new InspectorWindow(team, listener, target);
86
87 try {
88 self->_Init();
89 } catch (...) {
90 delete self;
91 throw;
92 }
93
94 return self;
95 }
96
97
98 void
_Init()99 InspectorWindow::_Init()
100 {
101 fLanguage = new CppLanguage();
102 fExpressionInfo = new ExpressionInfo();
103 fExpressionInfo->AddListener(this);
104
105 BScrollView* scrollView;
106
107 BMenu* hexMenu = new BMenu("Hex Mode");
108 BMessage* message = new BMessage(MSG_SET_HEX_MODE);
109 message->AddInt32("mode", HexModeNone);
110 BMenuItem* item = new BMenuItem("<None>", message, '0');
111 hexMenu->AddItem(item);
112 message = new BMessage(*message);
113 message->ReplaceInt32("mode", HexMode8BitInt);
114 item = new BMenuItem("8-bit integer", message, '1');
115 hexMenu->AddItem(item);
116 message = new BMessage(*message);
117 message->ReplaceInt32("mode", HexMode16BitInt);
118 item = new BMenuItem("16-bit integer", message, '2');
119 hexMenu->AddItem(item);
120 message = new BMessage(*message);
121 message->ReplaceInt32("mode", HexMode32BitInt);
122 item = new BMenuItem("32-bit integer", message, '3');
123 hexMenu->AddItem(item);
124 message = new BMessage(*message);
125 message->ReplaceInt32("mode", HexMode64BitInt);
126 item = new BMenuItem("64-bit integer", message, '4');
127 hexMenu->AddItem(item);
128
129 BMenu* endianMenu = new BMenu("Endian Mode");
130 message = new BMessage(MSG_SET_ENDIAN_MODE);
131 message->AddInt32("mode", EndianModeLittleEndian);
132 item = new BMenuItem("Little Endian", message, 'L');
133 endianMenu->AddItem(item);
134 message = new BMessage(*message);
135 message->ReplaceInt32("mode", EndianModeBigEndian);
136 item = new BMenuItem("Big Endian", message, 'B');
137 endianMenu->AddItem(item);
138
139 BMenu* textMenu = new BMenu("Text Mode");
140 message = new BMessage(MSG_SET_TEXT_MODE);
141 message->AddInt32("mode", TextModeNone);
142 item = new BMenuItem("<None>", message, 'N');
143 textMenu->AddItem(item);
144 message = new BMessage(*message);
145 message->ReplaceInt32("mode", TextModeASCII);
146 item = new BMenuItem("ASCII", message, 'A');
147 textMenu->AddItem(item);
148
149 BLayoutBuilder::Group<>(this, B_VERTICAL)
150 .SetInsets(B_USE_DEFAULT_SPACING)
151 .AddGroup(B_HORIZONTAL)
152 .Add(fAddressInput = new BTextControl("addrInput",
153 "Target Address:", "",
154 new BMessage(MSG_INSPECT_ADDRESS)))
155 .Add(fPreviousBlockButton = new BButton("navPrevious", "<",
156 new BMessage(MSG_NAVIGATE_PREVIOUS_BLOCK)))
157 .Add(fNextBlockButton = new BButton("navNext", ">",
158 new BMessage(MSG_NAVIGATE_NEXT_BLOCK)))
159 .End()
160 .AddGroup(B_HORIZONTAL)
161 .Add(fHexMode = new BMenuField("hexMode", "Hex Mode:",
162 hexMenu))
163 .AddGlue()
164 .Add(fEndianMode = new BMenuField("endianMode", "Endian Mode:",
165 endianMenu))
166 .AddGlue()
167 .Add(fTextMode = new BMenuField("viewMode", "Text Mode:",
168 textMenu))
169 .End()
170 .Add(scrollView = new BScrollView("memory scroll",
171 NULL, 0, false, true), 3.0f)
172 .AddGroup(B_HORIZONTAL)
173 .Add(fWritableBlockIndicator = new BStringView("writableIndicator",
174 _GetCurrentWritableIndicator()))
175 .AddGlue()
176 .Add(fEditBlockButton = new BButton("editBlock", "Edit",
177 new BMessage(MSG_EDIT_CURRENT_BLOCK)))
178 .Add(fCommitBlockButton = new BButton("commitBlock", "Commit",
179 new BMessage(MSG_COMMIT_MODIFIED_BLOCK)))
180 .Add(fRevertBlockButton = new BButton("revertBlock", "Revert",
181 new BMessage(MSG_REVERT_MODIFIED_BLOCK)))
182 .End()
183 .End();
184
185 fHexMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
186 fEndianMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
187 fTextMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
188
189 int32 targetEndian = fTeam->GetArchitecture()->IsBigEndian()
190 ? EndianModeBigEndian : EndianModeLittleEndian;
191
192 scrollView->SetTarget(fMemoryView = MemoryView::Create(fTeam, this));
193
194 fAddressInput->SetTarget(this);
195 fPreviousBlockButton->SetTarget(this);
196 fNextBlockButton->SetTarget(this);
197 fPreviousBlockButton->SetEnabled(false);
198 fNextBlockButton->SetEnabled(false);
199
200 fEditBlockButton->SetTarget(this);
201 fCommitBlockButton->SetTarget(this);
202 fRevertBlockButton->SetTarget(this);
203
204 fEditBlockButton->SetEnabled(false);
205 fCommitBlockButton->Hide();
206 fRevertBlockButton->Hide();
207
208 hexMenu->SetLabelFromMarked(true);
209 hexMenu->SetTargetForItems(fMemoryView);
210 endianMenu->SetLabelFromMarked(true);
211 endianMenu->SetTargetForItems(fMemoryView);
212 textMenu->SetLabelFromMarked(true);
213 textMenu->SetTargetForItems(fMemoryView);
214
215 // default to 8-bit format w/ text display
216 hexMenu->ItemAt(1)->SetMarked(true);
217 textMenu->ItemAt(1)->SetMarked(true);
218
219 if (targetEndian == EndianModeBigEndian)
220 endianMenu->ItemAt(1)->SetMarked(true);
221 else
222 endianMenu->ItemAt(0)->SetMarked(true);
223
224 fAddressInput->TextView()->MakeFocus(true);
225
226 AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, new BMessage(
227 MSG_NAVIGATE_PREVIOUS_BLOCK));
228 AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, new BMessage(
229 MSG_NAVIGATE_NEXT_BLOCK));
230 }
231
232
233 void
MessageReceived(BMessage * message)234 InspectorWindow::MessageReceived(BMessage* message)
235 {
236 switch (message->what) {
237 case MSG_THREAD_STATE_CHANGED:
238 {
239 ::Thread* thread;
240 if (message->FindPointer("thread",
241 reinterpret_cast<void**>(&thread)) != B_OK) {
242 break;
243 }
244
245 BReference< ::Thread> threadReference(thread, true);
246 if (thread->State() == THREAD_STATE_STOPPED) {
247 if (fCurrentBlock != NULL) {
248 _SetCurrentBlock(NULL);
249 _SetToAddress(fCurrentAddress);
250 }
251 }
252 break;
253 }
254 case MSG_INSPECT_ADDRESS:
255 {
256 target_addr_t address = 0;
257 if (message->FindUInt64("address", &address) != B_OK) {
258 if (fAddressInput->TextView()->TextLength() == 0)
259 break;
260
261 fExpressionInfo->SetTo(fAddressInput->Text());
262
263 fListener->ExpressionEvaluationRequested(fLanguage,
264 fExpressionInfo);
265 } else
266 _SetToAddress(address);
267 break;
268 }
269 case MSG_EXPRESSION_EVALUATED:
270 {
271 BString errorMessage;
272 BReference<ExpressionResult> reference;
273 ExpressionResult* value = NULL;
274 if (message->FindPointer("value",
275 reinterpret_cast<void**>(&value)) == B_OK) {
276 reference.SetTo(value, true);
277 if (value->Kind() == EXPRESSION_RESULT_KIND_PRIMITIVE) {
278 Value* primitive = value->PrimitiveValue();
279 BVariant variantValue;
280 primitive->ToVariant(variantValue);
281 if (variantValue.Type() == B_STRING_TYPE) {
282 errorMessage.SetTo(variantValue.ToString());
283 } else {
284 _SetToAddress(variantValue.ToUInt64());
285 break;
286 }
287 }
288 } else {
289 status_t result = message->FindInt32("result");
290 errorMessage.SetToFormat("Failed to evaluate expression: %s",
291 strerror(result));
292 }
293
294 BAlert* alert = new(std::nothrow) BAlert("Inspect Address",
295 errorMessage.String(), "Close");
296 if (alert != NULL)
297 alert->Go();
298 break;
299 }
300
301 case MSG_NAVIGATE_PREVIOUS_BLOCK:
302 case MSG_NAVIGATE_NEXT_BLOCK:
303 {
304 if (fCurrentBlock != NULL) {
305 target_addr_t address = fCurrentBlock->BaseAddress();
306 if (message->what == MSG_NAVIGATE_PREVIOUS_BLOCK)
307 address -= fCurrentBlock->Size();
308 else
309 address += fCurrentBlock->Size();
310
311 BMessage setMessage(MSG_INSPECT_ADDRESS);
312 setMessage.AddUInt64("address", address);
313 PostMessage(&setMessage);
314 }
315 break;
316 }
317 case MSG_MEMORY_BLOCK_RETRIEVED:
318 {
319 TeamMemoryBlock* block = NULL;
320 status_t result;
321 if (message->FindPointer("block",
322 reinterpret_cast<void **>(&block)) != B_OK
323 || message->FindInt32("result", &result) != B_OK) {
324 break;
325 }
326
327 if (result == B_OK) {
328 _SetCurrentBlock(block);
329 fPreviousBlockButton->SetEnabled(true);
330 fNextBlockButton->SetEnabled(true);
331 } else {
332 BString errorMessage;
333 errorMessage.SetToFormat("Unable to read address 0x%" B_PRIx64
334 ": %s", block->BaseAddress(), strerror(result));
335
336 BAlert* alert = new(std::nothrow) BAlert("Inspect address",
337 errorMessage.String(), "Close");
338 if (alert == NULL)
339 break;
340
341 alert->Go(NULL);
342 block->ReleaseReference();
343 }
344 break;
345 }
346 case MSG_EDIT_CURRENT_BLOCK:
347 {
348 _SetEditMode(true);
349 break;
350 }
351 case MSG_MEMORY_DATA_CHANGED:
352 {
353 if (fCurrentBlock == NULL)
354 break;
355
356 target_addr_t address;
357 if (message->FindUInt64("address", &address) == B_OK
358 && address >= fCurrentBlock->BaseAddress()
359 && address < fCurrentBlock->BaseAddress()
360 + fCurrentBlock->Size()) {
361 fCurrentBlock->Invalidate();
362 _SetEditMode(false);
363 fListener->InspectRequested(address, this);
364 }
365 break;
366 }
367 case MSG_COMMIT_MODIFIED_BLOCK:
368 {
369 // TODO: this could conceivably be extended to detect the
370 // individual modified regions and only write those back.
371 // That would require potentially submitting multiple separate
372 // write requests, and thus require tracking all the writes being
373 // waited upon for completion.
374 fListener->MemoryWriteRequested(fCurrentBlock->BaseAddress(),
375 fMemoryView->GetEditedData(), fCurrentBlock->Size());
376 break;
377 }
378 case MSG_REVERT_MODIFIED_BLOCK:
379 {
380 _SetEditMode(false);
381 break;
382 }
383 default:
384 {
385 BWindow::MessageReceived(message);
386 break;
387 }
388 }
389 }
390
391
392 bool
QuitRequested()393 InspectorWindow::QuitRequested()
394 {
395 BMessage settings(MSG_INSPECTOR_WINDOW_CLOSED);
396 SaveSettings(settings);
397
398 BMessenger(fTarget).SendMessage(&settings);
399 return true;
400 }
401
402
403 void
ThreadStateChanged(const Team::ThreadEvent & event)404 InspectorWindow::ThreadStateChanged(const Team::ThreadEvent& event)
405 {
406 BMessage message(MSG_THREAD_STATE_CHANGED);
407 BReference< ::Thread> threadReference(event.GetThread());
408 message.AddPointer("thread", threadReference.Get());
409
410 if (PostMessage(&message) == B_OK)
411 threadReference.Detach();
412 }
413
414
415 void
MemoryChanged(const Team::MemoryChangedEvent & event)416 InspectorWindow::MemoryChanged(const Team::MemoryChangedEvent& event)
417 {
418 BMessage message(MSG_MEMORY_DATA_CHANGED);
419 message.AddUInt64("address", event.GetTargetAddress());
420 message.AddUInt64("size", event.GetSize());
421
422 PostMessage(&message);
423 }
424
425
426 void
MemoryBlockRetrieved(TeamMemoryBlock * block)427 InspectorWindow::MemoryBlockRetrieved(TeamMemoryBlock* block)
428 {
429 BMessage message(MSG_MEMORY_BLOCK_RETRIEVED);
430 message.AddPointer("block", block);
431 message.AddInt32("result", B_OK);
432 PostMessage(&message);
433 }
434
435
436 void
MemoryBlockRetrievalFailed(TeamMemoryBlock * block,status_t result)437 InspectorWindow::MemoryBlockRetrievalFailed(TeamMemoryBlock* block,
438 status_t result)
439 {
440 BMessage message(MSG_MEMORY_BLOCK_RETRIEVED);
441 message.AddPointer("block", block);
442 message.AddInt32("result", result);
443 PostMessage(&message);
444 }
445
446
447 void
TargetAddressChanged(target_addr_t address)448 InspectorWindow::TargetAddressChanged(target_addr_t address)
449 {
450 AutoLocker<BLooper> lock(this);
451 if (lock.IsLocked()) {
452 fCurrentAddress = address;
453 BString computedAddress;
454 computedAddress.SetToFormat("0x%" B_PRIx64, address);
455 fAddressInput->SetText(computedAddress.String());
456 }
457 }
458
459
460 void
HexModeChanged(int32 newMode)461 InspectorWindow::HexModeChanged(int32 newMode)
462 {
463 AutoLocker<BLooper> lock(this);
464 if (lock.IsLocked()) {
465 BMenu* menu = fHexMode->Menu();
466 if (newMode < 0 || newMode > menu->CountItems())
467 return;
468 BMenuItem* item = menu->ItemAt(newMode);
469 item->SetMarked(true);
470 }
471 }
472
473
474 void
EndianModeChanged(int32 newMode)475 InspectorWindow::EndianModeChanged(int32 newMode)
476 {
477 AutoLocker<BLooper> lock(this);
478 if (lock.IsLocked()) {
479 BMenu* menu = fEndianMode->Menu();
480 if (newMode < 0 || newMode > menu->CountItems())
481 return;
482 BMenuItem* item = menu->ItemAt(newMode);
483 item->SetMarked(true);
484 }
485 }
486
487
488 void
TextModeChanged(int32 newMode)489 InspectorWindow::TextModeChanged(int32 newMode)
490 {
491 AutoLocker<BLooper> lock(this);
492 if (lock.IsLocked()) {
493 BMenu* menu = fTextMode->Menu();
494 if (newMode < 0 || newMode > menu->CountItems())
495 return;
496 BMenuItem* item = menu->ItemAt(newMode);
497 item->SetMarked(true);
498 }
499 }
500
501
502 void
ExpressionEvaluated(ExpressionInfo * info,status_t result,ExpressionResult * value)503 InspectorWindow::ExpressionEvaluated(ExpressionInfo* info, status_t result,
504 ExpressionResult* value)
505 {
506 BMessage message(MSG_EXPRESSION_EVALUATED);
507 message.AddInt32("result", result);
508 BReference<ExpressionResult> reference;
509 if (value != NULL) {
510 reference.SetTo(value);
511 message.AddPointer("value", value);
512 }
513
514 if (PostMessage(&message) == B_OK)
515 reference.Detach();
516 }
517
518
519 status_t
LoadSettings(const GuiTeamUiSettings & settings)520 InspectorWindow::LoadSettings(const GuiTeamUiSettings& settings)
521 {
522 AutoLocker<BLooper> lock(this);
523 if (!lock.IsLocked())
524 return B_ERROR;
525
526 BMessage inspectorSettings;
527 if (settings.Settings("inspectorWindow", inspectorSettings) != B_OK)
528 return B_OK;
529
530 BRect frameRect;
531 if (inspectorSettings.FindRect("frame", &frameRect) == B_OK) {
532 ResizeTo(frameRect.Width(), frameRect.Height());
533 MoveTo(frameRect.left, frameRect.top);
534 }
535
536 _LoadMenuFieldMode(fHexMode, "Hex", inspectorSettings);
537 _LoadMenuFieldMode(fEndianMode, "Endian", inspectorSettings);
538 _LoadMenuFieldMode(fTextMode, "Text", inspectorSettings);
539
540 return B_OK;
541 }
542
543
544 status_t
SaveSettings(BMessage & settings)545 InspectorWindow::SaveSettings(BMessage& settings)
546 {
547 AutoLocker<BLooper> lock(this);
548 if (!lock.IsLocked())
549 return B_ERROR;
550
551 settings.MakeEmpty();
552
553 status_t error = settings.AddRect("frame", Frame());
554 if (error != B_OK)
555 return error;
556
557 error = _SaveMenuFieldMode(fHexMode, "Hex", settings);
558 if (error != B_OK)
559 return error;
560
561 error = _SaveMenuFieldMode(fEndianMode, "Endian", settings);
562 if (error != B_OK)
563 return error;
564
565 error = _SaveMenuFieldMode(fTextMode, "Text", settings);
566 if (error != B_OK)
567 return error;
568
569 return B_OK;
570 }
571
572
573 void
_LoadMenuFieldMode(BMenuField * field,const char * name,const BMessage & settings)574 InspectorWindow::_LoadMenuFieldMode(BMenuField* field, const char* name,
575 const BMessage& settings)
576 {
577 BString fieldName;
578 int32 mode;
579 fieldName.SetToFormat("%sMode", name);
580 if (settings.FindInt32(fieldName.String(), &mode) == B_OK) {
581 BMenu* menu = field->Menu();
582 for (int32 i = 0; i < menu->CountItems(); i++) {
583 BInvoker* item = menu->ItemAt(i);
584 if (item->Message()->FindInt32("mode") == mode) {
585 item->Invoke();
586 break;
587 }
588 }
589 }
590 }
591
592
593 status_t
_SaveMenuFieldMode(BMenuField * field,const char * name,BMessage & settings)594 InspectorWindow::_SaveMenuFieldMode(BMenuField* field, const char* name,
595 BMessage& settings)
596 {
597 BMenuItem* item = field->Menu()->FindMarked();
598 if (item && item->Message()) {
599 int32 mode = item->Message()->FindInt32("mode");
600 BString fieldName;
601 fieldName.SetToFormat("%sMode", name);
602 return settings.AddInt32(fieldName.String(), mode);
603 }
604
605 return B_OK;
606 }
607
608
609 void
_SetToAddress(target_addr_t address)610 InspectorWindow::_SetToAddress(target_addr_t address)
611 {
612 fCurrentAddress = address;
613 if (fCurrentBlock == NULL
614 || !fCurrentBlock->Contains(address)) {
615 fListener->InspectRequested(address, this);
616 } else
617 fMemoryView->SetTargetAddress(fCurrentBlock, address);
618 }
619
620
621 void
_SetCurrentBlock(TeamMemoryBlock * block)622 InspectorWindow::_SetCurrentBlock(TeamMemoryBlock* block)
623 {
624 AutoLocker< ::Team> teamLocker(fTeam);
625 if (fCurrentBlock != NULL) {
626 fCurrentBlock->RemoveListener(this);
627 fCurrentBlock->ReleaseReference();
628 }
629
630 fCurrentBlock = block;
631 fMemoryView->SetTargetAddress(fCurrentBlock, fCurrentAddress);
632 _UpdateWritableOptions();
633 }
634
635
636 bool
_GetWritableState() const637 InspectorWindow::_GetWritableState() const
638 {
639 return fCurrentBlock != NULL ? fCurrentBlock->IsWritable() : false;
640 }
641
642
643 void
_SetEditMode(bool enabled)644 InspectorWindow::_SetEditMode(bool enabled)
645 {
646 if (enabled == fMemoryView->GetEditMode())
647 return;
648
649 status_t error = fMemoryView->SetEditMode(enabled);
650 if (error != B_OK)
651 return;
652
653 if (enabled) {
654 fEditBlockButton->Hide();
655 fCommitBlockButton->Show();
656 fRevertBlockButton->Show();
657 } else {
658 fEditBlockButton->Show();
659 fCommitBlockButton->Hide();
660 fRevertBlockButton->Hide();
661 }
662
663 fHexMode->SetEnabled(!enabled);
664 fEndianMode->SetEnabled(!enabled);
665
666 // while the block is being edited, disable block navigation controls.
667 fAddressInput->SetEnabled(!enabled);
668 fPreviousBlockButton->SetEnabled(!enabled);
669 fNextBlockButton->SetEnabled(!enabled);
670
671 InvalidateLayout();
672 }
673
674
675 void
_UpdateWritableOptions()676 InspectorWindow::_UpdateWritableOptions()
677 {
678 fEditBlockButton->SetEnabled(_GetWritableState());
679 _UpdateWritableIndicator();
680 }
681
682
683 void
_UpdateWritableIndicator()684 InspectorWindow::_UpdateWritableIndicator()
685 {
686 fWritableBlockIndicator->SetText(_GetCurrentWritableIndicator());
687 }
688
689
690 const char*
_GetCurrentWritableIndicator() const691 InspectorWindow::_GetCurrentWritableIndicator() const
692 {
693 static char buffer[32];
694 snprintf(buffer, sizeof(buffer), "Writable: %s", fCurrentBlock == NULL
695 ? "N/A" : fCurrentBlock->IsWritable() ? "Yes" : "No");
696
697 return buffer;
698 }
699