Annotation of nono/wx/wxplanemonitor.cpp, revision 1.1

1.1     ! root        1: //
        !             2: // nono
        !             3: // Copyright (C) 2020 nono project
        !             4: // Licensed under nono-license.txt
        !             5: //
        !             6: 
        !             7: //
        !             8: // プレーン VRAM モニターウィンドウ
        !             9: //
        !            10: 
        !            11: #include "wxplanemonitor.h"
        !            12: #include "wxcolor.h"
        !            13: #include "wxuimessage.h"
        !            14: #include "bt454.h"
        !            15: #include "planevram.h"
        !            16: #include "videoctlr.h"
        !            17: 
        !            18: enum {
        !            19:        ID_PLANE0 = IDGROUP_PLANEVRAM,
        !            20:        ID_PLANE1,
        !            21:        ID_PLANE2,
        !            22:        ID_PLANE3,
        !            23:        ID_PLANE4,
        !            24:        ID_PLANE5,
        !            25:        ID_PLANE6,
        !            26:        ID_PLANE7,
        !            27:        ID_PALETTE,
        !            28:        ID_SCALE,
        !            29: 
        !            30:        ID_local_end,   // 最後に置く (チェック用)
        !            31: };
        !            32: static_assert(ID_local_end - 1 <= (int)IDGROUP_PLANEVRAM_END, "ID exceeded");
        !            33: 
        !            34: #define ID_PLANE(id)   (ID_PLANE0 + (id))
        !            35: 
        !            36: //
        !            37: // プレーン VRAM ビットマップ
        !            38: //
        !            39: 
        !            40: // コンストラクタ
        !            41: WXPlaneVRAMBitmapPanel::WXPlaneVRAMBitmapPanel(wxWindow *parent)
        !            42:        : inherited(parent)
        !            43: {
        !            44:        plane_width  = gPlaneVRAM->GetComposite().GetWidth();
        !            45:        plane_height = gPlaneVRAM->GetComposite().GetHeight();
        !            46: 
        !            47:        // まず等倍でセット
        !            48:        scale = 1;
        !            49:        SetScale(1);
        !            50: 
        !            51:        // VM からの通知を受け取る
        !            52:        WXUIMessage::Connect(UIMessage::PALETTE, this,
        !            53:                wxCommandEventHandler(WXPlaneVRAMBitmapPanel::OnPaletteChanged));
        !            54: }
        !            55: 
        !            56: // デストラクタ
        !            57: WXPlaneVRAMBitmapPanel::~WXPlaneVRAMBitmapPanel()
        !            58: {
        !            59:        WXUIMessage::Disconnect(UIMessage::PALETTE,
        !            60:                wxCommandEventHandler(WXPlaneVRAMBitmapPanel::OnPaletteChanged));
        !            61: }
        !            62: 
        !            63: // XXX 実際にはこの辺かどこかで更新チェックをしないといけない。
        !            64: // 今は表示領域を毎回描画しているので無駄。
        !            65: 
        !            66: // ビットマッププレーンの内容を描画する
        !            67: void
        !            68: WXPlaneVRAMBitmapPanel::Draw()
        !            69: {
        !            70:        assert(view.GetRight()  < virtual_width);
        !            71:        assert(view.GetBottom() < virtual_height);
        !            72: 
        !            73:        const BitmapI8& src = gPlaneVRAM->GetComposite();
        !            74: 
        !            75:        if (scale > 1) {
        !            76:                Rect dr(0, 0, bitmap.GetWidth(), bitmap.GetHeight());
        !            77:                bitmap.DrawScale(dr, src, &pal[0], view.x, scale, 1, view.y, scale, 1);
        !            78:        } else {
        !            79:                bitmap.DrawBitmap(0, 0, src, &pal[0], view);
        !            80:        }
        !            81: }
        !            82: 
        !            83: // プレーン選択変更
        !            84: void
        !            85: WXPlaneVRAMBitmapPanel::EnablePlane(int plane, bool value)
        !            86: {
        !            87:        if (value) {
        !            88:                planemask |= 1U << plane;
        !            89:        } else {
        !            90:                planemask &= ~(1U << plane);
        !            91:        }
        !            92: 
        !            93:        // パレット生成
        !            94:        GenPalette();
        !            95: }
        !            96: 
        !            97: // パレット適用変更
        !            98: void
        !            99: WXPlaneVRAMBitmapPanel::EnablePalette(bool value)
        !           100: {
        !           101:        apply_palette = value;
        !           102: 
        !           103:        // パレット生成
        !           104:        GenPalette();
        !           105: }
        !           106: 
        !           107: // VM からのパレット変更通知
        !           108: void
        !           109: WXPlaneVRAMBitmapPanel::OnPaletteChanged(wxCommandEvent& event)
        !           110: {
        !           111:        // パレット生成
        !           112:        GenPalette();
        !           113: }
        !           114: 
        !           115: // パレット生成
        !           116: void
        !           117: WXPlaneVRAMBitmapPanel::GenPalette()
        !           118: {
        !           119:        if (apply_palette) {
        !           120:                // パレットを適用する場合
        !           121:                const ColorXBGR *vmpal;
        !           122:                // 雑に X680x0/LUNA 対応
        !           123:                if (gVideoCtlr) {
        !           124:                        vmpal = gVideoCtlr->GetHostPalette();
        !           125:                } else {
        !           126:                        vmpal = gBT454->GetHostPalette();
        !           127:                }
        !           128:                const Color bg = vmpal[0].ToColor();
        !           129:                for (int i = 0; i < pal.size(); i++) {
        !           130:                        if ((i & planemask)) {
        !           131:                                int n = i % 16;
        !           132:                                pal[i] = vmpal[n].ToColor();
        !           133:                        } else {
        !           134:                                pal[i] = bg;
        !           135:                        }
        !           136:                }
        !           137:        } else {
        !           138:                // パレットを適用しない場合
        !           139:                for (int i = 0; i < pal.size(); i++) {
        !           140:                        if ((i & planemask)) {
        !           141:                                pal[i] = Color(255, 255, 255);
        !           142:                        } else {
        !           143:                                pal[i] = Color(0, 0, 0);
        !           144:                        }
        !           145:                }
        !           146:        }
        !           147: }
        !           148: 
        !           149: // 倍率を設定
        !           150: void
        !           151: WXPlaneVRAMBitmapPanel::SetScale(int scale_)
        !           152: {
        !           153:        int before = scale;
        !           154: 
        !           155:        scale = scale_;
        !           156: 
        !           157:        // 仮想画面の大きさを計算
        !           158:        virtual_width = plane_width * scale;
        !           159:        virtual_height = plane_height * scale;
        !           160: 
        !           161:        // ビューのスケール変換は前後のスケールがいるのでここでやる
        !           162:        view.x = view.x * scale / before;
        !           163:        view.y = view.y * scale / before;
        !           164:        view.w = view.w * scale / before;
        !           165:        view.h = view.h * scale / before;
        !           166: 
        !           167:        SetMaxClientSize(wxSize(virtual_width, virtual_height));
        !           168: }
        !           169: 
        !           170: // スクロール位置変更通知
        !           171: void
        !           172: WXPlaneVRAMBitmapPanel::OnScroll(wxScrollEvent& event)
        !           173: {
        !           174:        int orient = event.GetOrientation();
        !           175:        int pos = event.GetPosition();
        !           176: 
        !           177:        if (orient == wxHORIZONTAL) {
        !           178:                view.x = pos;
        !           179:        } else {
        !           180:                view.y = pos;
        !           181:        }
        !           182: }
        !           183: 
        !           184: 
        !           185: //
        !           186: // プレーン VRAM パネル
        !           187: //
        !           188: 
        !           189: // イベントテーブル
        !           190: wxBEGIN_EVENT_TABLE(WXPlaneVRAMPanel, inherited)
        !           191:        EVT_SIZE(WXPlaneVRAMPanel::OnSize)
        !           192:        EVT_TIMER(wxID_ANY, WXPlaneVRAMPanel::OnTimer)
        !           193: wxEND_EVENT_TABLE()
        !           194: 
        !           195: // コンストラクタ
        !           196: WXPlaneVRAMPanel::WXPlaneVRAMPanel(wxWindow *parent)
        !           197:        : inherited(parent)
        !           198: {
        !           199:        // 表示パネル
        !           200:        viewctl = new WXPlaneVRAMBitmapPanel(this);
        !           201: 
        !           202:        // スクロールバーを自分で持つ (配置は Layout() で行う)
        !           203:        vscroll = new WXScrollBar(this, wxID_ANY, wxSB_VERTICAL);
        !           204:        hscroll = new WXScrollBar(this, wxID_ANY, wxSB_HORIZONTAL);
        !           205: 
        !           206:        // 右下の空き地
        !           207:        corner = new WXBitmapPanel(this);
        !           208: 
        !           209:        // Sizer 使わず自前でレイアウトする。
        !           210:        SetAutoLayout(true);
        !           211: 
        !           212:        // 最小値は適当
        !           213:        int vscr_width  = vscroll->GetClientSize().x;
        !           214:        int hscr_height = hscroll->GetClientSize().y;
        !           215:        SetMinClientSize(wxSize(16 + vscr_width, 16 + hscr_height));
        !           216:        SetMaxClientSize(wxSize(viewctl->virtual_width + vscr_width,
        !           217:                viewctl->virtual_height + hscr_height));
        !           218: 
        !           219:        // スクロールバーからの通知イベントは表示パネルへ回す
        !           220:        vscroll->Connect(NONO_EVT_SCROLL,
        !           221:                wxScrollEventHandler(WXPlaneVRAMBitmapPanel::OnScroll), NULL, viewctl);
        !           222:        hscroll->Connect(NONO_EVT_SCROLL,
        !           223:                wxScrollEventHandler(WXPlaneVRAMBitmapPanel::OnScroll), NULL, viewctl);
        !           224: 
        !           225:        // 更新タイマーはモニターパネル同様の 30Hz にしておく。
        !           226:        timer.SetOwner(this);
        !           227:        timer.Start(33);
        !           228: }
        !           229: 
        !           230: // デストラクタ
        !           231: WXPlaneVRAMPanel::~WXPlaneVRAMPanel()
        !           232: {
        !           233: }
        !           234: 
        !           235: bool
        !           236: WXPlaneVRAMPanel::Layout()
        !           237: {
        !           238:        if (inherited::Layout() == false) {
        !           239:                return false;
        !           240:        }
        !           241: 
        !           242:        // コントロールを配置
        !           243:        const wxSize size = GetClientSize();
        !           244:        int vscr_width  = vscroll->GetClientSize().x;
        !           245:        int hscr_height = hscroll->GetClientSize().y;
        !           246:        int view_width  = size.x - vscr_width;
        !           247:        int view_height = size.y - hscr_height;
        !           248:        viewctl->SetSize(0, 0, view_width, view_height);
        !           249:        vscroll->SetSize(view_width, 0, vscr_width, view_height);
        !           250:        hscroll->SetSize(0, view_height, view_width, hscr_height);
        !           251:        corner->SetSize(view_width, view_height, vscr_width, hscr_height);
        !           252: 
        !           253:        // XXX 内部のコントロールのサイズが確定してからでないと DoSize() できない
        !           254:        DoSize(size);
        !           255: 
        !           256:        return true;
        !           257: }
        !           258: 
        !           259: // サイズ変更イベント
        !           260: void
        !           261: WXPlaneVRAMPanel::OnSize(wxSizeEvent& event)
        !           262: {
        !           263:        // Mac ではウィンドウ作成時に 0 以下の値が来ることがある。
        !           264:        // GTK では 1 で来ることがある。
        !           265:        wxSize size = event.GetSize();
        !           266:        if (size.x <= 1 || size.y <= 1) {
        !           267:                return;
        !           268:        }
        !           269: 
        !           270:        // Layout() を呼ぶために必要
        !           271:        event.Skip();
        !           272: 
        !           273:        // XXX レイアウトが終わった後でないと DoSize() できない
        !           274: //     DoSize(size);
        !           275: }
        !           276: 
        !           277: // サイズ変更処理。パネルサイズと倍率が変わったら呼ぶこと。
        !           278: // 倍率が変わった場合もスクロールバーの再設定とかがあるため。
        !           279: void
        !           280: WXPlaneVRAMPanel::DoSize(wxSize size)
        !           281: {
        !           282:        wxSize viewsize = viewctl->GetClientSize();
        !           283:        int virtual_width = viewctl->virtual_width;
        !           284:        int virtual_height = viewctl->virtual_height;
        !           285:        Rect& view = viewctl->view;
        !           286: 
        !           287:        // スクロールバーが右端まで行っている場合は右を固定したように見せる。
        !           288:        // ただし全体が見えている場合は除く。
        !           289: 
        !           290:        if (view.x > 0 &&
        !           291:            (view.x + view.w == virtual_width ||
        !           292:             view.x + viewsize.x >= virtual_width))
        !           293:        {
        !           294:                view.x = virtual_width - viewsize.x;
        !           295:                if (view.x < 0) {
        !           296:                        view.x = 0;
        !           297:                }
        !           298:        }
        !           299:        view.w = viewsize.x;
        !           300: 
        !           301:        // 下端も同様
        !           302:        if (view.y > 0 &&
        !           303:            (view.y + view.h >= virtual_height ||
        !           304:             view.y + size.y >= virtual_height))
        !           305:        {
        !           306:                view.y = virtual_height - viewsize.y;
        !           307:                if (view.y < 0) {
        !           308:                        view.y = 0;
        !           309:                }
        !           310:        }
        !           311:        view.h = viewsize.y;
        !           312: 
        !           313:        // スクロールバー(横)を再設定
        !           314:        assert(hscroll);
        !           315:        int hpos = view.x;
        !           316:        int hthumbsize = view.w;
        !           317:        int hrange = virtual_width;
        !           318:        int hpagesize = view.w;
        !           319:        hscroll->SetScrollParam(hpos, hthumbsize, hrange, hpagesize);
        !           320: 
        !           321:        // スクロールバー(縦)を再設定
        !           322:        assert(vscroll);
        !           323:        int vpos = view.y;
        !           324:        int vthumbsize = view.h;
        !           325:        int vrange = virtual_height;
        !           326:        int vpagesize = view.h;
        !           327:        vscroll->SetScrollParam(vpos, vthumbsize, vrange, vpagesize);
        !           328: }
        !           329: 
        !           330: // タイマーイベント
        !           331: void
        !           332: WXPlaneVRAMPanel::OnTimer(wxTimerEvent& event)
        !           333: {
        !           334:        Refresh();
        !           335: }
        !           336: 
        !           337: // プレーン選択変更
        !           338: void
        !           339: WXPlaneVRAMPanel::EnablePlane(int plane, bool value)
        !           340: {
        !           341:        viewctl->EnablePlane(plane, value);
        !           342: }
        !           343: 
        !           344: // パレット適用変更
        !           345: void
        !           346: WXPlaneVRAMPanel::EnablePalette(bool value)
        !           347: {
        !           348:        viewctl->EnablePalette(value);
        !           349: }
        !           350: 
        !           351: // 倍率を設定
        !           352: void
        !           353: WXPlaneVRAMPanel::SetScale(int scale_)
        !           354: {
        !           355:        viewctl->SetScale(scale_);
        !           356: 
        !           357:        int vscr_width  = vscroll->GetClientSize().x;
        !           358:        int hscr_height = hscroll->GetClientSize().y;
        !           359:        SetMaxClientSize(wxSize(viewctl->virtual_width + vscr_width,
        !           360:                viewctl->virtual_height + hscr_height));
        !           361: 
        !           362:        DoSize(GetSize());
        !           363: }
        !           364: 
        !           365: 
        !           366: //
        !           367: // プレーン VRAM ウィンドウ
        !           368: //
        !           369: 
        !           370: // イベントテーブル
        !           371: wxBEGIN_EVENT_TABLE(WXPlaneVRAMWindow, inherited)
        !           372:        EVT_COMMAND_RANGE(ID_PLANE0, ID_PLANE7, wxEVT_CHECKBOX,
        !           373:                WXPlaneVRAMWindow::OnPlane)
        !           374:        EVT_CHECKBOX(ID_PALETTE, WXPlaneVRAMWindow::OnApplyPalette)
        !           375:        EVT_CHOICE(ID_SCALE, WXPlaneVRAMWindow::OnScale)
        !           376: wxEND_EVENT_TABLE()
        !           377: 
        !           378: // コンストラクタ
        !           379: WXPlaneVRAMWindow::WXPlaneVRAMWindow(wxWindow *parent, const wxString& name)
        !           380:        : inherited(parent, wxID_ANY, name,
        !           381:                inherited::DEFAULT_STYLE | wxRESIZE_BORDER)
        !           382: {
        !           383:        auto *topsizer = new wxBoxSizer(wxVERTICAL);
        !           384: 
        !           385:        // 上にコントロールパネル(のまず下敷き)
        !           386:        ctrlpanel = new WXBitmapPanel(this);
        !           387:        topsizer->Add(ctrlpanel, 0, wxEXPAND);
        !           388: 
        !           389:        // コントロールパネル用の横 Sizer
        !           390:        auto *ctrlbox = new wxBoxSizer(wxHORIZONTAL);
        !           391: 
        !           392:        // プレーン選択チェックボックス (#3, #2, #1, #0 の順に並べる)
        !           393:        for (int i = planesw.size() - 1; i >= 0; i--) {
        !           394:                planesw[i] = new wxCheckBox(ctrlpanel, ID_PLANE(i),
        !           395:                        string_format("#%d", i));
        !           396:                ctrlbox->Add(planesw[i], 0, wxALIGN_CENTER);
        !           397:        }
        !           398: 
        !           399:        // パレット合成チェックボックス
        !           400:        applysw = new wxCheckBox(ctrlpanel, ID_PALETTE, _("Apply palette"));
        !           401:        ctrlbox->Add(applysw, 0, wxALIGN_CENTER);
        !           402: 
        !           403:        // セパレータを出したいのだが…
        !           404:        ctrlbox->Add(new wxStaticText(ctrlpanel, wxID_ANY, "|"), 0, wxALIGN_CENTER);
        !           405: 
        !           406:        // 倍率
        !           407:        wxString scale_choice[] = {
        !           408:                "x1",
        !           409:                "x2",
        !           410:                "x4",
        !           411:        };
        !           412:        scalesw = new wxChoice(ctrlpanel, ID_SCALE,
        !           413:                wxDefaultPosition, wxDefaultSize,
        !           414:                countof(scale_choice), scale_choice);
        !           415:        ctrlbox->Add(scalesw, 0, wxALIGN_CENTER);
        !           416: 
        !           417:        // sizer と下敷きパネルを紐付ける
        !           418:        ctrlpanel->SetSizer(ctrlbox);
        !           419:        ctrlbox->SetSizeHints(ctrlpanel);
        !           420: 
        !           421:        viewpanel = new WXPlaneVRAMPanel(this);
        !           422:        topsizer->Add(viewpanel, 1, wxEXPAND);
        !           423: 
        !           424:        SetSizer(topsizer);
        !           425: 
        !           426:        // 最小サイズは上コントロール部の横幅が最小基準
        !           427:        wxSize minsize = ctrlpanel->GetMinClientSize();
        !           428:        minsize.y += viewpanel->GetMinClientSize().y;
        !           429:        SetMinClientSize(minsize);
        !           430:        // 最大サイズは DoScale() 内で設定している
        !           431: 
        !           432:        // 1bpp ならプレーン選択はすべて無効、4bpp ならすべて有効。
        !           433:        if (gPlaneVRAM->GetPlaneCount() == 1) {
        !           434:                for (int i = 0; i < planesw.size(); i++) {
        !           435:                        planesw[i]->Enable(false);
        !           436:                }
        !           437:        }
        !           438: 
        !           439:        // 初期値をコントロールにセットする
        !           440:        if (sticky_used) {
        !           441:                // 前回の値があればそれを使う。
        !           442:                for (int i = 0; i < planesw.size(); i++) {
        !           443:                        planesw[i]->SetValue(sticky_plane[i]);
        !           444:                }
        !           445:                applysw->SetValue(sticky_apply);
        !           446:                scalesw->SetSelection(sticky_scale);
        !           447:        } else {
        !           448:                if (gPlaneVRAM->GetPlaneCount() == 1) {
        !           449:                        // 初回 1bpp なら #0 プレーンのみオン。
        !           450:                        for (int i = 0; i < planesw.size(); i++) {
        !           451:                                planesw[i]->SetValue((i == 0));
        !           452:                        }
        !           453:                        applysw->SetValue(true);
        !           454:                } else {
        !           455:                        // 初回 4bpp なら全プレーンオン。
        !           456:                        for (int i = 0; i < planesw.size(); i++) {
        !           457:                                planesw[i]->SetValue(true);
        !           458:                        }
        !           459:                        applysw->SetValue(true);
        !           460:                }
        !           461:                scalesw->SetSelection(0);
        !           462:        }
        !           463: 
        !           464:        // SetValue() ではイベントが飛ばないので、
        !           465:        // 直接呼び出してイベントが来たことにする。うーん…。
        !           466:        for (int i = 0; i < planesw.size(); i++) {
        !           467:                DoPlane(i, planesw[i]->IsChecked());
        !           468:        }
        !           469:        DoApplyPalette(applysw->IsChecked());
        !           470:        DoScale(scalesw->GetSelection());
        !           471: }
        !           472: 
        !           473: // デストラクタ
        !           474: WXPlaneVRAMWindow::~WXPlaneVRAMWindow()
        !           475: {
        !           476: }
        !           477: 
        !           478: void
        !           479: WXPlaneVRAMWindow::FontChanged()
        !           480: {
        !           481:        // 親 (wxSubWindow) の FontChanged() を実行しないようにするための
        !           482:        // 空のオーバーライド。親の FontChanged() が呼ぶ DoSize() が
        !           483:        // ウィンドウサイズを変更しようとするが、ここはテキストフォントを
        !           484:        // 持っていないのでその操作は不要。うーん。
        !           485: }
        !           486: 
        !           487: // プレーン選択のチェックボックスイベント
        !           488: void
        !           489: WXPlaneVRAMWindow::OnPlane(wxCommandEvent& event)
        !           490: {
        !           491:        int plane = event.GetId() - ID_PLANE0;
        !           492:        bool value = event.IsChecked();
        !           493: 
        !           494:        DoPlane(plane, value);
        !           495: }
        !           496: 
        !           497: // プレーン選択 ON/OFF 処理
        !           498: void
        !           499: WXPlaneVRAMWindow::DoPlane(int plane, bool value)
        !           500: {
        !           501:        // コントロールに指示
        !           502:        viewpanel->EnablePlane(plane, value);
        !           503: 
        !           504:        // 値を保存
        !           505:        sticky_plane[plane] = value;
        !           506:        sticky_used = true;
        !           507: }
        !           508: 
        !           509: // パレット合成のチェックボックスイベント
        !           510: void
        !           511: WXPlaneVRAMWindow::OnApplyPalette(wxCommandEvent& event)
        !           512: {
        !           513:        bool value = event.IsChecked();
        !           514: 
        !           515:        DoApplyPalette(value);
        !           516: }
        !           517: 
        !           518: // パレット合成 ON/OFF 処理
        !           519: void
        !           520: WXPlaneVRAMWindow::DoApplyPalette(bool value)
        !           521: {
        !           522:        // コントロールに指示
        !           523:        viewpanel->EnablePalette(value);
        !           524: 
        !           525:        // 値を保存
        !           526:        sticky_apply = value;
        !           527:        sticky_used = true;
        !           528: }
        !           529: 
        !           530: // 倍率変更イベント
        !           531: void
        !           532: WXPlaneVRAMWindow::OnScale(wxCommandEvent& event)
        !           533: {
        !           534:        int scale_index = event.GetSelection();
        !           535: 
        !           536:        DoScale(scale_index);
        !           537: }
        !           538: 
        !           539: // 倍率変更処理
        !           540: void
        !           541: WXPlaneVRAMWindow::DoScale(int scale_index)
        !           542: {
        !           543:        viewpanel->SetScale(1 << scale_index);
        !           544: 
        !           545:        // 最大サイズは表示部の最大サイズから
        !           546:        wxSize maxsize = viewpanel->GetMaxClientSize();
        !           547:        maxsize.y += ctrlpanel->GetMinClientSize().y;
        !           548:        SetMaxClientSize(maxsize);
        !           549: 
        !           550:        // 保存しておくのはインデックスのほう
        !           551:        sticky_scale = scale_index;
        !           552: }
        !           553: 
        !           554: // static 変数
        !           555: std::array<bool, WXPlaneVRAMWindow::NPLANES> WXPlaneVRAMWindow::sticky_plane;
        !           556: bool WXPlaneVRAMWindow::sticky_apply;
        !           557: int WXPlaneVRAMWindow::sticky_scale;
        !           558: bool WXPlaneVRAMWindow::sticky_used;

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.