sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_BWin.h (20053B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
      4 
      5   This software is provided 'as-is', without any express or implied
      6   warranty.  In no event will the authors be held liable for any damages
      7   arising from the use of this software.
      8 
      9   Permission is granted to anyone to use this software for any purpose,
     10   including commercial applications, and to alter it and redistribute it
     11   freely, subject to the following restrictions:
     12 
     13   1. The origin of this software must not be misrepresented; you must not
     14      claim that you wrote the original software. If you use this software
     15      in a product, an acknowledgment in the product documentation would be
     16      appreciated but is not required.
     17   2. Altered source versions must be plainly marked as such, and must not be
     18      misrepresented as being the original software.
     19   3. This notice may not be removed or altered from any source distribution.
     20 */
     21 
     22 #ifndef SDL_BWin_h_
     23 #define SDL_BWin_h_
     24 
     25 #ifdef __cplusplus
     26 extern "C" {
     27 #endif
     28 
     29 #include "../../SDL_internal.h"
     30 #include "SDL.h"
     31 #include "SDL_syswm.h"
     32 #include "SDL_bframebuffer.h"
     33 
     34 #ifdef __cplusplus
     35 }
     36 #endif
     37 
     38 #include <stdio.h>
     39 #include <AppKit.h>
     40 #include <InterfaceKit.h>
     41 #include <game/DirectWindow.h>
     42 #if SDL_VIDEO_OPENGL
     43 #include <opengl/GLView.h>
     44 #endif
     45 #include "SDL_events.h"
     46 #include "../../main/haiku/SDL_BApp.h"
     47 
     48 
     49 enum WinCommands {
     50     BWIN_MOVE_WINDOW,
     51     BWIN_RESIZE_WINDOW,
     52     BWIN_SHOW_WINDOW,
     53     BWIN_HIDE_WINDOW,
     54     BWIN_MAXIMIZE_WINDOW,
     55     BWIN_MINIMIZE_WINDOW,
     56     BWIN_RESTORE_WINDOW,
     57     BWIN_SET_TITLE,
     58     BWIN_SET_BORDERED,
     59     BWIN_SET_RESIZABLE,
     60     BWIN_FULLSCREEN
     61 };
     62 
     63 
     64 class SDL_BWin:public BDirectWindow
     65 {
     66   public:
     67     /* Constructor/Destructor */
     68     SDL_BWin(BRect bounds, window_look look, uint32 flags)
     69         : BDirectWindow(bounds, "Untitled", look, B_NORMAL_WINDOW_FEEL, flags)
     70     {
     71         _last_buttons = 0;
     72 
     73 #if SDL_VIDEO_OPENGL
     74         _SDL_GLView = NULL;
     75         _gl_type = 0;
     76 #endif
     77         _shown = false;
     78         _inhibit_resize = false;
     79         _mouse_focused = false;
     80         _prev_frame = NULL;
     81 
     82         /* Handle framebuffer stuff */
     83         _connected = _connection_disabled = false;
     84         _buffer_created = _buffer_dirty = false;
     85         _trash_window_buffer = false;
     86         _buffer_locker = new BLocker();
     87         _bitmap = NULL;
     88         _clips = NULL;
     89         _num_clips = 0;
     90 
     91 #ifdef DRAWTHREAD
     92         _draw_thread_id = spawn_thread(HAIKU_DrawThread, "drawing_thread",
     93                             B_NORMAL_PRIORITY, (void*) this);
     94         resume_thread(_draw_thread_id);
     95 #endif
     96     }
     97 
     98     virtual ~ SDL_BWin()
     99     {
    100         Lock();
    101         _connection_disabled = true;
    102         int32 result;
    103 
    104 #if SDL_VIDEO_OPENGL
    105         if (_SDL_GLView) {
    106             _SDL_GLView->UnlockGL();
    107             RemoveChild(_SDL_GLView);   /* Why was this outside the if
    108                                             statement before? */
    109         }
    110 
    111 #endif
    112         Unlock();
    113 #if SDL_VIDEO_OPENGL
    114         if (_SDL_GLView) {
    115             delete _SDL_GLView;
    116         }
    117 #endif
    118 
    119         delete _prev_frame;
    120 
    121         /* Clean up framebuffer stuff */
    122         _buffer_locker->Lock();
    123 #ifdef DRAWTHREAD
    124         wait_for_thread(_draw_thread_id, &result);
    125 #endif
    126         free(_clips);
    127         delete _buffer_locker;
    128     }
    129 
    130 
    131     /* * * * * OpenGL functionality * * * * */
    132 #if SDL_VIDEO_OPENGL
    133     virtual BGLView *CreateGLView(Uint32 gl_flags) {
    134         Lock();
    135         if (_SDL_GLView == NULL) {
    136             _SDL_GLView = new BGLView(Bounds(), "SDL GLView",
    137                                      B_FOLLOW_ALL_SIDES,
    138                                      (B_WILL_DRAW | B_FRAME_EVENTS),
    139                                      gl_flags);
    140             _gl_type = gl_flags;
    141         }
    142         AddChild(_SDL_GLView);
    143         _SDL_GLView->SetEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS, B_NO_POINTER_HISTORY);
    144         _SDL_GLView->EnableDirectMode(true);
    145         _SDL_GLView->LockGL();  /* "New" GLViews are created */
    146         Unlock();
    147         return (_SDL_GLView);
    148     }
    149 
    150     virtual void RemoveGLView() {
    151         Lock();
    152         if(_SDL_GLView) {
    153             _SDL_GLView->UnlockGL();
    154             RemoveChild(_SDL_GLView);
    155         }
    156         Unlock();
    157     }
    158 
    159     virtual void SwapBuffers(void) {
    160         _SDL_GLView->UnlockGL();
    161         _SDL_GLView->LockGL();
    162         _SDL_GLView->SwapBuffers();
    163     }
    164 #endif
    165 
    166     /* * * * * Framebuffering* * * * */
    167     virtual void DirectConnected(direct_buffer_info *info) {
    168         if(!_connected && _connection_disabled) {
    169             return;
    170         }
    171 
    172         /* Determine if the pixel buffer is usable after this update */
    173         _trash_window_buffer =      _trash_window_buffer
    174                                 || ((info->buffer_state & B_BUFFER_RESIZED)
    175                                 || (info->buffer_state & B_BUFFER_RESET)
    176                                 || (info->driver_state == B_MODE_CHANGED));
    177         LockBuffer();
    178 
    179         switch(info->buffer_state & B_DIRECT_MODE_MASK) {
    180         case B_DIRECT_START:
    181             _connected = true;
    182 
    183         case B_DIRECT_MODIFY:
    184             if (info->clip_list_count > _num_clips)
    185             {
    186                 if(_clips) {
    187                     free(_clips);
    188                     _clips = NULL;
    189                 }
    190             }
    191 
    192             _num_clips = info->clip_list_count;
    193             if (_clips == NULL)
    194                 _clips = (clipping_rect *)malloc(_num_clips*sizeof(clipping_rect));
    195             if(_clips) {
    196                 memcpy(_clips, info->clip_list,
    197                     _num_clips*sizeof(clipping_rect));
    198 
    199                 _bits = (uint8*) info->bits;
    200                 _row_bytes = info->bytes_per_row;
    201                 _bounds = info->window_bounds;
    202                 _bytes_per_px = info->bits_per_pixel / 8;
    203                 _buffer_dirty = true;
    204             }
    205             break;
    206 
    207         case B_DIRECT_STOP:
    208             _connected = false;
    209             break;
    210         }
    211 #if SDL_VIDEO_OPENGL
    212         if(_SDL_GLView) {
    213             _SDL_GLView->DirectConnected(info);
    214         }
    215 #endif
    216 
    217 
    218         /* Call the base object directconnected */
    219         BDirectWindow::DirectConnected(info);
    220 
    221         UnlockBuffer();
    222 
    223     }
    224 
    225 
    226 
    227 
    228     /* * * * * Event sending * * * * */
    229     /* Hook functions */
    230     virtual void FrameMoved(BPoint origin) {
    231         /* Post a message to the BApp so that it can handle the window event */
    232         BMessage msg(BAPP_WINDOW_MOVED);
    233         msg.AddInt32("window-x", (int)origin.x);
    234         msg.AddInt32("window-y", (int)origin.y);
    235         _PostWindowEvent(msg);
    236 
    237         /* Perform normal hook operations */
    238         BDirectWindow::FrameMoved(origin);
    239     }
    240 
    241     virtual void FrameResized(float width, float height) {
    242         /* Post a message to the BApp so that it can handle the window event */
    243         BMessage msg(BAPP_WINDOW_RESIZED);
    244 
    245         msg.AddInt32("window-w", (int)width + 1);
    246         msg.AddInt32("window-h", (int)height + 1);
    247         _PostWindowEvent(msg);
    248 
    249         /* Perform normal hook operations */
    250         BDirectWindow::FrameResized(width, height);
    251     }
    252 
    253     virtual bool QuitRequested() {
    254         BMessage msg(BAPP_WINDOW_CLOSE_REQUESTED);
    255         _PostWindowEvent(msg);
    256 
    257         /* We won't allow a quit unless asked by DestroyWindow() */
    258         return false;
    259     }
    260 
    261     virtual void WindowActivated(bool active) {
    262         BMessage msg(BAPP_KEYBOARD_FOCUS);  /* Mouse focus sold separately */
    263         msg.AddBool("focusGained", active);
    264         _PostWindowEvent(msg);
    265     }
    266 
    267     virtual void Zoom(BPoint origin,
    268                 float width,
    269                 float height) {
    270         BMessage msg(BAPP_MAXIMIZE);    /* Closest thing to maximization Haiku has */
    271         _PostWindowEvent(msg);
    272 
    273         /* Before the window zooms, record its size */
    274         if( !_prev_frame )
    275             _prev_frame = new BRect(Frame());
    276 
    277         /* Perform normal hook operations */
    278         BDirectWindow::Zoom(origin, width, height);
    279     }
    280 
    281     /* Member functions */
    282     virtual void Show() {
    283         while(IsHidden()) {
    284             BDirectWindow::Show();
    285         }
    286         _shown = true;
    287 
    288         BMessage msg(BAPP_SHOW);
    289         _PostWindowEvent(msg);
    290     }
    291 
    292     virtual void Hide() {
    293         BDirectWindow::Hide();
    294         _shown = false;
    295 
    296         BMessage msg(BAPP_HIDE);
    297         _PostWindowEvent(msg);
    298     }
    299 
    300     virtual void Minimize(bool minimize) {
    301         BDirectWindow::Minimize(minimize);
    302         int32 minState = (minimize ? BAPP_MINIMIZE : BAPP_RESTORE);
    303 
    304         BMessage msg(minState);
    305         _PostWindowEvent(msg);
    306     }
    307 
    308 
    309     /* BView message interruption */
    310     virtual void DispatchMessage(BMessage * msg, BHandler * target)
    311     {
    312         BPoint where;   /* Used by mouse moved */
    313         int32 buttons;  /* Used for mouse button events */
    314         int32 key;      /* Used for key events */
    315 
    316         switch (msg->what) {
    317         case B_MOUSE_MOVED:
    318             int32 transit;
    319             if (msg->FindPoint("where", &where) == B_OK
    320                 && msg->FindInt32("be:transit", &transit) == B_OK) {
    321                 _MouseMotionEvent(where, transit);
    322             }
    323             break;
    324 
    325         case B_MOUSE_DOWN:
    326             if (msg->FindInt32("buttons", &buttons) == B_OK) {
    327                 _MouseButtonEvent(buttons, SDL_PRESSED);
    328             }
    329             break;
    330 
    331         case B_MOUSE_UP:
    332             if (msg->FindInt32("buttons", &buttons) == B_OK) {
    333                 _MouseButtonEvent(buttons, SDL_RELEASED);
    334             }
    335             break;
    336 
    337         case B_MOUSE_WHEEL_CHANGED:
    338             float x, y;
    339             if (msg->FindFloat("be:wheel_delta_x", &x) == B_OK
    340                 && msg->FindFloat("be:wheel_delta_y", &y) == B_OK) {
    341                     _MouseWheelEvent((int)x, (int)y);
    342             }
    343             break;
    344 
    345         case B_KEY_DOWN:
    346             {
    347                 int32 i = 0;
    348                 int8 byte;
    349                 int8 bytes[4] = { 0, 0, 0, 0 };
    350                 while (i < 4 && msg->FindInt8("byte", i, &byte) == B_OK) {
    351                     bytes[i] = byte;
    352                     i++;
    353                 }
    354                 if (msg->FindInt32("key", &key) == B_OK) {
    355                     _KeyEvent((SDL_Scancode)key, &bytes[0], i, SDL_PRESSED);
    356                 }
    357             }
    358             break;
    359             
    360         case B_UNMAPPED_KEY_DOWN:      /* modifier keys are unmapped */
    361             if (msg->FindInt32("key", &key) == B_OK) {
    362                 _KeyEvent((SDL_Scancode)key, NULL, 0, SDL_PRESSED);
    363             }
    364             break;
    365 
    366         case B_KEY_UP:
    367         case B_UNMAPPED_KEY_UP:        /* modifier keys are unmapped */
    368             if (msg->FindInt32("key", &key) == B_OK) {
    369                 _KeyEvent(key, NULL, 0, SDL_RELEASED);
    370             }
    371             break;
    372 
    373         default:
    374             /* move it after switch{} so it's always handled
    375                that way we keep Haiku features like:
    376                - CTRL+Q to close window (and other shortcuts)
    377                - PrintScreen to make screenshot into /boot/home
    378                - etc.. */
    379             /* BDirectWindow::DispatchMessage(msg, target); */
    380             break;
    381         }
    382 
    383         BDirectWindow::DispatchMessage(msg, target);
    384     }
    385 
    386     /* Handle command messages */
    387     virtual void MessageReceived(BMessage* message) {
    388         switch (message->what) {
    389             /* Handle commands from SDL */
    390             case BWIN_SET_TITLE:
    391                 _SetTitle(message);
    392                 break;
    393             case BWIN_MOVE_WINDOW:
    394                 _MoveTo(message);
    395                 break;
    396             case BWIN_RESIZE_WINDOW:
    397                 _ResizeTo(message);
    398                 break;
    399             case BWIN_SET_BORDERED:
    400                 _SetBordered(message);
    401                 break;
    402             case BWIN_SET_RESIZABLE:
    403                 _SetResizable(message);
    404                 break;
    405             case BWIN_SHOW_WINDOW:
    406                 Show();
    407                 break;
    408             case BWIN_HIDE_WINDOW:
    409                 Hide();
    410                 break;
    411             case BWIN_MAXIMIZE_WINDOW:
    412                 BWindow::Zoom();
    413                 break;
    414             case BWIN_MINIMIZE_WINDOW:
    415                 Minimize(true);
    416                 break;
    417             case BWIN_RESTORE_WINDOW:
    418                 _Restore();
    419                 break;
    420             case BWIN_FULLSCREEN:
    421                 _SetFullScreen(message);
    422                 break;
    423             default:
    424                 /* Perform normal message handling */
    425                 BDirectWindow::MessageReceived(message);
    426                 break;
    427         }
    428 
    429     }
    430 
    431 
    432 
    433     /* Accessor methods */
    434     bool IsShown() { return _shown; }
    435     int32 GetID() { return _id; }
    436     uint32 GetRowBytes() { return _row_bytes; }
    437     int32 GetFbX() { return _bounds.left; }
    438     int32 GetFbY() { return _bounds.top; }
    439     bool ConnectionEnabled() { return !_connection_disabled; }
    440     bool Connected() { return _connected; }
    441     clipping_rect *GetClips() { return _clips; }
    442     int32 GetNumClips() { return _num_clips; }
    443     uint8* GetBufferPx() { return _bits; }
    444     int32 GetBytesPerPx() { return _bytes_per_px; }
    445     bool CanTrashWindowBuffer() { return _trash_window_buffer; }
    446     bool BufferExists() { return _buffer_created; }
    447     bool BufferIsDirty() { return _buffer_dirty; }
    448     BBitmap *GetBitmap() { return _bitmap; }
    449 #if SDL_VIDEO_OPENGL
    450     BGLView *GetGLView() { return _SDL_GLView; }
    451     Uint32 GetGLType() { return _gl_type; }
    452 #endif
    453 
    454     /* Setter methods */
    455     void SetID(int32 id) { _id = id; }
    456     void SetBufferExists(bool bufferExists) { _buffer_created = bufferExists; }
    457     void LockBuffer() { _buffer_locker->Lock(); }
    458     void UnlockBuffer() { _buffer_locker->Unlock(); }
    459     void SetBufferDirty(bool bufferDirty) { _buffer_dirty = bufferDirty; }
    460     void SetTrashBuffer(bool trash) { _trash_window_buffer = trash;     }
    461     void SetBitmap(BBitmap *bitmap) { _bitmap = bitmap; }
    462 
    463 
    464 private:
    465     /* Event redirection */
    466     void _MouseMotionEvent(BPoint &where, int32 transit) {
    467         if(transit == B_EXITED_VIEW) {
    468             /* Change mouse focus */
    469             if(_mouse_focused) {
    470                 _MouseFocusEvent(false);
    471             }
    472         } else {
    473             /* Change mouse focus */
    474             if (!_mouse_focused) {
    475                 _MouseFocusEvent(true);
    476             }
    477             BMessage msg(BAPP_MOUSE_MOVED);
    478             msg.AddInt32("x", (int)where.x);
    479             msg.AddInt32("y", (int)where.y);
    480 
    481             _PostWindowEvent(msg);
    482         }
    483     }
    484 
    485     void _MouseFocusEvent(bool focusGained) {
    486         _mouse_focused = focusGained;
    487         BMessage msg(BAPP_MOUSE_FOCUS);
    488         msg.AddBool("focusGained", focusGained);
    489         _PostWindowEvent(msg);
    490 
    491 /* FIXME: Why were these here?
    492  if false: be_app->SetCursor(B_HAND_CURSOR);
    493  if true:  SDL_SetCursor(NULL); */
    494     }
    495 
    496     void _MouseButtonEvent(int32 buttons, Uint8 state) {
    497         int32 buttonStateChange = buttons ^ _last_buttons;
    498 
    499         if(buttonStateChange & B_PRIMARY_MOUSE_BUTTON) {
    500             _SendMouseButton(SDL_BUTTON_LEFT, state);
    501         }
    502         if(buttonStateChange & B_SECONDARY_MOUSE_BUTTON) {
    503             _SendMouseButton(SDL_BUTTON_RIGHT, state);
    504         }
    505         if(buttonStateChange & B_TERTIARY_MOUSE_BUTTON) {
    506             _SendMouseButton(SDL_BUTTON_MIDDLE, state);
    507         }
    508 
    509         _last_buttons = buttons;
    510     }
    511 
    512     void _SendMouseButton(int32 button, int32 state) {
    513         BMessage msg(BAPP_MOUSE_BUTTON);
    514         msg.AddInt32("button-id", button);
    515         msg.AddInt32("button-state", state);
    516         _PostWindowEvent(msg);
    517     }
    518 
    519     void _MouseWheelEvent(int32 x, int32 y) {
    520         /* Create a message to pass along to the BeApp thread */
    521         BMessage msg(BAPP_MOUSE_WHEEL);
    522         msg.AddInt32("xticks", x);
    523         msg.AddInt32("yticks", y);
    524         _PostWindowEvent(msg);
    525     }
    526 
    527     void _KeyEvent(int32 keyCode, const int8 *keyUtf8, const ssize_t & len, int32 keyState) {
    528         /* Create a message to pass along to the BeApp thread */
    529         BMessage msg(BAPP_KEY);
    530         msg.AddInt32("key-state", keyState);
    531         msg.AddInt32("key-scancode", keyCode);
    532         if (keyUtf8 != NULL) {
    533             msg.AddData("key-utf8", B_INT8_TYPE, (const void*)keyUtf8, len);
    534         }
    535         be_app->PostMessage(&msg);
    536     }
    537 
    538     void _RepaintEvent() {
    539         /* Force a repaint: Call the SDL exposed event */
    540         BMessage msg(BAPP_REPAINT);
    541         _PostWindowEvent(msg);
    542     }
    543     void _PostWindowEvent(BMessage &msg) {
    544         msg.AddInt32("window-id", _id);
    545         be_app->PostMessage(&msg);
    546     }
    547 
    548     /* Command methods (functions called upon by SDL) */
    549     void _SetTitle(BMessage *msg) {
    550         const char *title;
    551         if(
    552             msg->FindString("window-title", &title) != B_OK
    553         ) {
    554             return;
    555         }
    556         SetTitle(title);
    557     }
    558 
    559     void _MoveTo(BMessage *msg) {
    560         int32 x, y;
    561         if(
    562             msg->FindInt32("window-x", &x) != B_OK ||
    563             msg->FindInt32("window-y", &y) != B_OK
    564         ) {
    565             return;
    566         }
    567         MoveTo(x, y);
    568     }
    569 
    570     void _ResizeTo(BMessage *msg) {
    571         int32 w, h;
    572         if(
    573             msg->FindInt32("window-w", &w) != B_OK ||
    574             msg->FindInt32("window-h", &h) != B_OK
    575         ) {
    576             return;
    577         }
    578         ResizeTo(w, h);
    579     }
    580 
    581     void _SetBordered(BMessage *msg) {
    582         bool bEnabled;
    583         if(msg->FindBool("window-border", &bEnabled) != B_OK) {
    584             return;
    585         }
    586         SetLook(bEnabled ? B_TITLED_WINDOW_LOOK : B_NO_BORDER_WINDOW_LOOK);
    587     }
    588 
    589     void _SetResizable(BMessage *msg) {
    590         bool bEnabled;
    591         if(msg->FindBool("window-resizable", &bEnabled) != B_OK) {
    592             return;
    593         }
    594         if (bEnabled) {
    595             SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_ZOOMABLE));
    596         } else {
    597             SetFlags(Flags() | (B_NOT_RESIZABLE | B_NOT_ZOOMABLE));
    598         }
    599     }
    600 
    601     void _Restore() {
    602         if(IsMinimized()) {
    603             Minimize(false);
    604         } else if(IsHidden()) {
    605             Show();
    606         } else if(_prev_frame != NULL) {    /* Zoomed */
    607             MoveTo(_prev_frame->left, _prev_frame->top);
    608             ResizeTo(_prev_frame->Width(), _prev_frame->Height());
    609         }
    610     }
    611 
    612     void _SetFullScreen(BMessage *msg) {
    613         bool fullscreen;
    614         if(
    615             msg->FindBool("fullscreen", &fullscreen) != B_OK
    616         ) {
    617             return;
    618         }
    619         SetFullScreen(fullscreen);
    620     }
    621 
    622     /* Members */
    623 #if SDL_VIDEO_OPENGL
    624     BGLView * _SDL_GLView;
    625     Uint32 _gl_type;
    626 #endif
    627 
    628     int32 _last_buttons;
    629     int32 _id;  /* Window id used by SDL_BApp */
    630     bool  _mouse_focused;       /* Does this window have mouse focus? */
    631     bool  _shown;
    632     bool  _inhibit_resize;
    633 
    634     BRect *_prev_frame; /* Previous position and size of the window */
    635 
    636     /* Framebuffer members */
    637     bool            _connected,
    638                     _connection_disabled,
    639                     _buffer_created,
    640                     _buffer_dirty,
    641                     _trash_window_buffer;
    642     uint8          *_bits;
    643     uint32          _row_bytes;
    644     clipping_rect   _bounds;
    645     BLocker        *_buffer_locker;
    646     clipping_rect  *_clips;
    647     uint32          _num_clips;
    648     int32           _bytes_per_px;
    649     thread_id       _draw_thread_id;
    650 
    651     BBitmap        *_bitmap;
    652 };
    653 
    654 
    655 /* FIXME:
    656  * An explanation of framebuffer flags.
    657  *
    658  * _connected -           Original variable used to let the drawing thread know
    659  *                         when changes are being made to the other framebuffer
    660  *                         members.
    661  * _connection_disabled - Used to signal to the drawing thread that the window
    662  *                         is closing, and the thread should exit.
    663  * _buffer_created -      True if the current buffer is valid
    664  * _buffer_dirty -        True if the window should be redrawn.
    665  * _trash_window_buffer - True if the window buffer needs to be trashed partway
    666  *                         through a draw cycle.  Occurs when the previous
    667  *                         buffer provided by DirectConnected() is invalidated.
    668  */
    669 #endif /* SDL_BWin_h_ */
    670 
    671 /* vi: set ts=4 sw=4 expandtab: */