/* * Copyright 2001-2006, Haiku. * Distributed under the terms of the MIT License. * * Authors: * Adrian Oanca * Axel Dörfler, axeld@pinc-software.de * Stephan Aßmus, */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_WIN #ifdef DEBUG_WIN # include # define STRACE(x) printf x #else # define STRACE(x) ; #endif struct BWindow::unpack_cookie { unpack_cookie(); BMessage* message; int32 index; BHandler* focus; int32 focus_token; int32 last_view_token; bool found_focus; bool tokens_scanned; }; class BWindow::Shortcut { public: Shortcut(uint32 key, uint32 modifiers, BMenuItem* item); Shortcut(uint32 key, uint32 modifiers, BMessage* message, BHandler* target); ~Shortcut(); bool Matches(uint32 key, uint32 modifiers) const; BMenuItem* MenuItem() const { return fMenuItem; } BMessage* Message() const { return fMessage; } BHandler* Target() const { return fTarget; } static uint32 AllowedModifiers(); static uint32 PrepareKey(uint32 key); static uint32 PrepareModifiers(uint32 modifiers); private: uint32 fKey; uint32 fModifiers; BMenuItem* fMenuItem; BMessage* fMessage; BHandler* fTarget; }; using BPrivate::gDefaultTokens; static property_info sWindowPropInfo[] = { { "Feel", { B_GET_PROPERTY, B_SET_PROPERTY }, { B_DIRECT_SPECIFIER }, NULL, 0, { B_INT32_TYPE } }, { "Flags", { B_GET_PROPERTY, B_SET_PROPERTY }, { B_DIRECT_SPECIFIER }, NULL, 0, { B_INT32_TYPE } }, { "Frame", { B_GET_PROPERTY, B_SET_PROPERTY }, { B_DIRECT_SPECIFIER }, NULL, 0, { B_RECT_TYPE } }, { "Hidden", { B_GET_PROPERTY, B_SET_PROPERTY }, { B_DIRECT_SPECIFIER }, NULL, 0, { B_BOOL_TYPE } }, { "Look", { B_GET_PROPERTY, B_SET_PROPERTY }, { B_DIRECT_SPECIFIER }, NULL, 0, { B_INT32_TYPE } }, { "Title", { B_GET_PROPERTY, B_SET_PROPERTY }, { B_DIRECT_SPECIFIER }, NULL, 0, { B_STRING_TYPE } }, { "Workspaces", { B_GET_PROPERTY, B_SET_PROPERTY }, { B_DIRECT_SPECIFIER }, NULL, 0, { B_INT32_TYPE} }, { "MenuBar", {}, { B_DIRECT_SPECIFIER }, NULL, 0, {} }, { "View", {}, {}, NULL, 0, {} }, { "Minimize", { B_GET_PROPERTY, B_SET_PROPERTY }, { B_DIRECT_SPECIFIER }, NULL, 0, { B_BOOL_TYPE } }, {} }; void _set_menu_sem_(BWindow *window, sem_id sem) { if (window != NULL) window->fMenuSem = sem; } // #pragma mark - BWindow::unpack_cookie::unpack_cookie() : message((BMessage*)~0UL), // message == NULL is our exit condition index(0), focus_token(B_NULL_TOKEN), last_view_token(B_NULL_TOKEN), found_focus(false), tokens_scanned(false) { } // #pragma mark - BWindow::Shortcut::Shortcut(uint32 key, uint32 modifiers, BMenuItem* item) : fKey(PrepareKey(key)), fModifiers(PrepareModifiers(modifiers)), fMenuItem(item), fMessage(NULL), fTarget(NULL) { } BWindow::Shortcut::Shortcut(uint32 key, uint32 modifiers, BMessage* message, BHandler* target) : fKey(PrepareKey(key)), fModifiers(PrepareModifiers(modifiers)), fMenuItem(NULL), fMessage(message), fTarget(target) { } BWindow::Shortcut::~Shortcut() { // we own the message, if any delete fMessage; } bool BWindow::Shortcut::Matches(uint32 key, uint32 modifiers) const { return fKey == key && fModifiers == modifiers; } /*static*/ uint32 BWindow::Shortcut::AllowedModifiers() { return B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY | B_CONTROL_KEY | B_MENU_KEY; } /*static*/ uint32 BWindow::Shortcut::PrepareModifiers(uint32 modifiers) { return (modifiers & AllowedModifiers()) | B_COMMAND_KEY; } /*static*/ uint32 BWindow::Shortcut::PrepareKey(uint32 key) { return tolower(key); // TODO: support unicode and/or more intelligent key mapping } // #pragma mark - BWindow::BWindow(BRect frame, const char* title, window_type type, uint32 flags, uint32 workspace) : BLooper(title) { window_look look; window_feel feel; _DecomposeType(type, &look, &feel); _InitData(frame, title, look, feel, flags, workspace); } BWindow::BWindow(BRect frame, const char* title, window_look look, window_feel feel, uint32 flags, uint32 workspace) : BLooper(title) { _InitData(frame, title, look, feel, flags, workspace); } BWindow::BWindow(BMessage* data) : BLooper(data) { data->FindRect("_frame", &fFrame); const char *title; data->FindString("_title", &title); window_look look; data->FindInt32("_wlook", (int32 *)&look); window_feel feel; data->FindInt32("_wfeel", (int32 *)&feel); if (data->FindInt32("_flags", (int32 *)&fFlags) != B_OK) fFlags = 0; uint32 workspaces; data->FindInt32("_wspace", (int32 *)&workspaces); uint32 type; if (data->FindInt32("_type", (int32*)&type) == B_OK) _DecomposeType((window_type)type, &fLook, &fFeel); // connect to app_server and initialize data _InitData(fFrame, title, look, feel, fFlags, workspaces); if (data->FindFloat("_zoom", 0, &fMaxZoomWidth) == B_OK && data->FindFloat("_zoom", 1, &fMaxZoomHeight) == B_OK) SetZoomLimits(fMaxZoomWidth, fMaxZoomHeight); if (data->FindFloat("_sizel", 0, &fMinWidth) == B_OK && data->FindFloat("_sizel", 1, &fMinHeight) == B_OK && data->FindFloat("_sizel", 2, &fMaxWidth) == B_OK && data->FindFloat("_sizel", 3, &fMaxHeight) == B_OK) SetSizeLimits(fMinWidth, fMaxWidth, fMinHeight, fMaxHeight); if (data->FindInt64("_pulse", &fPulseRate) == B_OK) SetPulseRate(fPulseRate); BMessage msg; int32 i = 0; while ( data->FindMessage("_views", i++, &msg) == B_OK){ BArchivable *obj = instantiate_object(&msg); BView *child = dynamic_cast(obj); if (child) AddChild(child); } } BWindow::BWindow(BRect frame, int32 bitmapToken) : BLooper("offscreen bitmap") { // TODO: Implement for real _DecomposeType(B_UNTYPED_WINDOW, &fLook, &fFeel); _InitData(frame, "offscreen", fLook, fFeel, 0, 0, bitmapToken); } BWindow::~BWindow() { Lock(); // Wait if a menu is still tracking if (fMenuSem > 0) { while (acquire_sem(fMenuSem) == B_INTERRUPTED) ; } fTopView->RemoveSelf(); delete fTopView; // remove all remaining shortcuts int32 shortCutCount = fShortcuts.CountItems(); for (int32 i = 0; i < shortCutCount; i++) { delete (Shortcut*)fShortcuts.ItemAtFast(i); } // TODO: release other dynamically-allocated objects free(fTitle); // disable pulsing SetPulseRate(0); // tell app_server about our demise fLink->StartMessage(AS_DELETE_WINDOW); // sync with the server so that for example // a BBitmap can be sure that there are no // more pending messages that are executed // after the bitmap is deleted (which uses // a different link and server side thread) int32 code; fLink->FlushWithReply(code); // the sender port belongs to the app_server delete_port(fLink->ReceiverPort()); delete fLink; } BArchivable * BWindow::Instantiate(BMessage *data) { if (!validate_instantiation(data , "BWindow")) return NULL; return new BWindow(data); } status_t BWindow::Archive(BMessage* data, bool deep) const { status_t retval = BLooper::Archive(data, deep); if (retval != B_OK) return retval; data->AddRect("_frame", fFrame); data->AddString("_title", fTitle); data->AddInt32("_wlook", fLook); data->AddInt32("_wfeel", fFeel); if (fFlags) data->AddInt32("_flags", fFlags); data->AddInt32("_wspace", (uint32)Workspaces()); if (!_ComposeType(fLook, fFeel)) data->AddInt32("_type", (uint32)Type()); if (fMaxZoomWidth != 32768.0 || fMaxZoomHeight != 32768.0) { data->AddFloat("_zoom", fMaxZoomWidth); data->AddFloat("_zoom", fMaxZoomHeight); } if (fMinWidth != 0.0 || fMinHeight != 0.0 || fMaxWidth != 32768.0 || fMaxHeight != 32768.0) { data->AddFloat("_sizel", fMinWidth); data->AddFloat("_sizel", fMinHeight); data->AddFloat("_sizel", fMaxWidth); data->AddFloat("_sizel", fMaxHeight); } if (fPulseRate != 500000) data->AddInt64("_pulse", fPulseRate); if (deep) { int32 noOfViews = CountChildren(); for (int32 i = 0; i < noOfViews; i++){ BMessage childArchive; if (ChildAt(i)->Archive(&childArchive, deep) == B_OK) data->AddMessage("_views", &childArchive); } } return B_OK; } void BWindow::Quit() { if (!IsLocked()) { const char *name = Name(); if (!name) name = "no-name"; printf("ERROR - you must Lock a looper before calling Quit(), " "team=%ld, looper=%s\n", Team(), name); } // Try to lock if (!Lock()){ // We're toast already return; } while (!IsHidden()) { Hide(); } if (fFlags & B_QUIT_ON_WINDOW_CLOSE) be_app->PostMessage(B_QUIT_REQUESTED); BLooper::Quit(); } void BWindow::AddChild(BView *child, BView *before) { BAutolock locker(this); fTopView->AddChild(child, before); } bool BWindow::RemoveChild(BView *child) { BAutolock locker(this); return fTopView->RemoveChild(child); } int32 BWindow::CountChildren() const { BAutolock _(const_cast(this)); return fTopView->CountChildren(); } BView * BWindow::ChildAt(int32 index) const { BAutolock _(const_cast(this)); return fTopView->ChildAt(index); } void BWindow::Minimize(bool minimize) { if (IsModal() || IsFloating() || fMinimized == minimize || !Lock()) return; fMinimized = minimize; fLink->StartMessage(AS_MINIMIZE_WINDOW); fLink->Attach(minimize); fLink->Attach(fShowLevel); fLink->Flush(); Unlock(); } status_t BWindow::SendBehind(const BWindow *window) { if (!window || !Lock()) return B_ERROR; fLink->StartMessage(AS_SEND_BEHIND); fLink->Attach(_get_object_token_(window)); fLink->Attach(Team()); status_t status = B_ERROR; fLink->FlushWithReply(status); Unlock(); return status; } void BWindow::Flush() const { if (const_cast(this)->Lock()) { fLink->Flush(); const_cast(this)->Unlock(); } } void BWindow::Sync() const { if (!const_cast(this)->Lock()) return; fLink->StartMessage(AS_SYNC); // waiting for the reply is the actual syncing int32 code; fLink->FlushWithReply(code); const_cast(this)->Unlock(); } void BWindow::DisableUpdates() { if (Lock()) { fLink->StartMessage(AS_DISABLE_UPDATES); fLink->Flush(); Unlock(); } } void BWindow::EnableUpdates() { if (Lock()) { fLink->StartMessage(AS_ENABLE_UPDATES); fLink->Flush(); Unlock(); } } void BWindow::BeginViewTransaction() { if (Lock()) { if (fInTransaction) { Unlock(); return; } fInTransaction = true; Unlock(); } } void BWindow::EndViewTransaction() { if (Lock()) { if (!fInTransaction) { Unlock(); return; } fLink->Flush(); fInTransaction = false; Unlock(); } } bool BWindow::IsFront() const { if (IsActive()) return true; if (IsModal()) return true; return false; } void BWindow::MessageReceived(BMessage *msg) { if (!msg->HasSpecifiers()) { if (msg->what == B_KEY_DOWN) _KeyboardNavigation(); return BLooper::MessageReceived(msg); } BMessage replyMsg(B_REPLY); bool handled = false; switch (msg->what) { case B_GET_PROPERTY: case B_SET_PROPERTY: { BMessage specifier; int32 what; const char *prop; int32 index; if (msg->GetCurrentSpecifier(&index, &specifier, &what, &prop) != B_OK) break; if (!strcmp(prop, "Feel")) { if (msg->what == B_GET_PROPERTY) { replyMsg.AddInt32("result", (uint32)Feel()); handled = true; } else { uint32 newFeel; if (msg->FindInt32("data", (int32 *)&newFeel) == B_OK) { SetFeel((window_feel)newFeel); handled = true; } } } else if (!strcmp(prop, "Flags")) { if (msg->what == B_GET_PROPERTY) { replyMsg.AddInt32("result", Flags()); handled = true; } else { uint32 newFlags; if (msg->FindInt32("data", (int32 *)&newFlags) == B_OK) { SetFlags(newFlags); handled = true; } } } else if (!strcmp(prop, "Frame")) { if (msg->what == B_GET_PROPERTY) { replyMsg.AddRect("result", Frame()); handled = true; } else { BRect newFrame; if (msg->FindRect("data", &newFrame) == B_OK) { MoveTo(newFrame.LeftTop()); ResizeTo(newFrame.Width(), newFrame.Height()); handled = true; } } } else if (!strcmp(prop, "Hidden")) { if (msg->what == B_GET_PROPERTY) { replyMsg.AddBool("result", IsHidden()); handled = true; } else { bool hide; if (msg->FindBool("data", &hide) == B_OK) { if (hide) { if (!IsHidden()) Hide(); } else if (IsHidden()) Show(); handled = true; } } } else if (!strcmp(prop, "Look")) { if (msg->what == B_GET_PROPERTY) { replyMsg.AddInt32("result", (uint32)Look()); handled = true; } else { uint32 newLook; if (msg->FindInt32("data", (int32 *)&newLook) == B_OK) { SetLook((window_look)newLook); handled = true; } } } else if (!strcmp(prop, "Title")) { if (msg->what == B_GET_PROPERTY) { replyMsg.AddString("result", Title()); handled = true; } else { const char *newTitle = NULL; if (msg->FindString("data", &newTitle) == B_OK) { SetTitle(newTitle); handled = true; } } } else if (!strcmp(prop, "Workspaces")) { if (msg->what == B_GET_PROPERTY) { replyMsg.AddInt32( "result", Workspaces()); handled = true; } else { uint32 newWorkspaces; if (msg->FindInt32("data", (int32 *)&newWorkspaces) == B_OK) { SetWorkspaces(newWorkspaces); handled = true; } } } else if (!strcmp(prop, "Minimize")) { if (msg->what == B_GET_PROPERTY) { replyMsg.AddBool("result", IsMinimized()); handled = true; } else { bool minimize; if (msg->FindBool("data", &minimize) == B_OK) { Minimize(minimize); handled = true; } } } break; } } if (handled) { if (msg->what == B_SET_PROPERTY) replyMsg.AddInt32("error", B_OK); } else { replyMsg.what = B_MESSAGE_NOT_UNDERSTOOD; replyMsg.AddInt32("error", B_BAD_SCRIPT_SYNTAX); replyMsg.AddString("message", "Didn't understand the specifier(s)"); } msg->SendReply(&replyMsg); } void BWindow::DispatchMessage(BMessage *msg, BHandler *target) { if (!msg) return; switch (msg->what) { case B_ZOOM: Zoom(); break; case B_MINIMIZE: { bool minimize; if (msg->FindBool("minimize", &minimize) == B_OK) Minimize(minimize); break; } case B_WINDOW_RESIZED: { int32 width, height; if (msg->FindInt32("width", &width) == B_OK && msg->FindInt32("height", &height) == B_OK) { // combine with pending resize notifications BMessage* pendingMessage; while ((pendingMessage = MessageQueue()->FindMessage(B_WINDOW_RESIZED, 0))) { if (pendingMessage != msg) { int32 nextWidth; if (pendingMessage->FindInt32("width", &nextWidth) == B_OK) width = nextWidth; int32 nextHeight; if (pendingMessage->FindInt32("height", &nextHeight) == B_OK) height = nextHeight; MessageQueue()->RemoveMessage(pendingMessage); // TODO: the BeBook says that MessageQueue::RemoveMessage() deletes the message! delete pendingMessage; // this deletes the first *additional* message // fCurrentMessage is safe } else { MessageQueue()->RemoveMessage(pendingMessage); } } if (width != fFrame.Width() || height != fFrame.Height()) { // NOTE: we might have already handled the resize // in an _UPDATE_ message fFrame.right = fFrame.left + width; fFrame.bottom = fFrame.top + height; _AdoptResize(); // FrameResized(width, height); } // call hook function anyways // TODO: When a window is resized programmatically, // it receives this message, and maybe it is wise to // keep the asynchronous nature of this process to // not risk breaking any apps. FrameResized(width, height); } break; } case B_WINDOW_MOVED: { BPoint origin; if (msg->FindPoint("where", &origin) == B_OK) { if (fFrame.LeftTop() != origin) { // NOTE: we might have already handled the move // in an _UPDATE_ message fFrame.OffsetTo(origin); // FrameMoved(origin); } // call hook function anyways // TODO: When a window is moved programmatically, // it receives this message, and maybe it is wise to // keep the asynchronous nature of this process to // not risk breaking any apps. FrameMoved(origin); } break; } case B_WINDOW_ACTIVATED: if (target == this) { bool active; if (msg->FindBool("active", &active) == B_OK && active != fActive) { fActive = active; WindowActivated(active); // call hook function 'WindowActivated(bool)' for all // views attached to this window. fTopView->_Activate(active); } } else target->MessageReceived(msg); break; case B_SCREEN_CHANGED: if (target == this) { BRect frame; uint32 mode; if (msg->FindRect("frame", &frame) == B_OK && msg->FindInt32("mode", (int32 *)&mode) == B_OK) ScreenChanged(frame, (color_space)mode); } else target->MessageReceived(msg); break; case B_WORKSPACE_ACTIVATED: if (target == this) { uint32 workspace; bool active; if (msg->FindInt32("workspace", (int32 *)&workspace) == B_OK && msg->FindBool("active", &active) == B_OK) WorkspaceActivated(workspace, active); } else target->MessageReceived(msg); break; case B_WORKSPACES_CHANGED: if (target == this) { uint32 oldWorkspace, newWorkspace; if (msg->FindInt32("old", (int32 *)&oldWorkspace) == B_OK && msg->FindInt32("new", (int32 *)&newWorkspace) == B_OK) WorkspacesChanged(oldWorkspace, newWorkspace); } else target->MessageReceived(msg); break; case B_KEY_DOWN: { uint32 modifiers; int32 rawChar; const char *string = NULL; msg->FindInt32("modifiers", (int32*)&modifiers); msg->FindInt32("raw_char", &rawChar); msg->FindString("bytes", &string); // TODO: cannot use "string" here if we support having different // font encoding per view (it's supposed to be converted by // _HandleKeyDown() one day) if (!_HandleKeyDown(string[0], (uint32)modifiers)) { if (BView* view = dynamic_cast(target)) view->KeyDown(string, strlen(string)); else target->MessageReceived(msg); } break; } case B_KEY_UP: { const char *string = NULL; msg->FindString("bytes", &string); // TODO: same as above if (BView* view = dynamic_cast(target)) view->KeyUp(string, strlen(string)); else target->MessageReceived(msg); break; } case B_MOUSE_DOWN: { if (BView *view = dynamic_cast(target)) { BPoint where; msg->FindPoint("be:view_where", &where); view->MouseDown(where); } else target->MessageReceived(msg); break; } case B_MOUSE_UP: { if (BView *view = dynamic_cast(target)) { BPoint where; msg->FindPoint("be:view_where", &where); view->MouseUp(where); } else target->MessageReceived(msg); break; } case B_MOUSE_MOVED: { if (BView *view = dynamic_cast(target)) { BPoint where; uint32 buttons; uint32 transit; msg->FindPoint("be:view_where", &where); msg->FindInt32("buttons", (int32*)&buttons); msg->FindInt32("be:transit", (int32*)&transit); #if 0 bigtime_t when; if (msg->FindInt64("when", (int64*)&when) < B_OK) printf("BWindow B_MOUSE_MOVED no when\n"); else if (system_time() - when > 5000) printf("BWindow B_MOUSE_MOVED lagging behind\n"); #endif BMessage* dragMessage = NULL; if (msg->HasMessage("be:drag_message")) { dragMessage = new BMessage(); if (msg->FindMessage("be:drag_message", dragMessage) != B_OK) { delete dragMessage; dragMessage = NULL; } } view->MouseMoved(where, transit, dragMessage); delete dragMessage; } else target->MessageReceived(msg); break; } case B_PULSE: if (target == this && fPulseRunner) { fTopView->_Pulse(); fLink->Flush(); } else target->MessageReceived(msg); break; case _UPDATE_: { //bigtime_t now = system_time(); STRACE(("info:BWindow handling _UPDATE_.\n")); BRect updateRect; fLink->StartMessage(AS_BEGIN_UPDATE); fInTransaction = true; int32 code; if (fLink->FlushWithReply(code) == B_OK && code == B_OK) { // read current window position and size first, // the update rect is in screen coordinates... // so we need to be up to date BPoint origin; fLink->Read(&origin); float width; float height; fLink->Read(&width); fLink->Read(&height); if (origin != fFrame.LeftTop()) { // TODO: remove code duplicatation with // B_WINDOW_MOVED case... //printf("window position was not up to date\n"); fFrame.OffsetTo(origin); FrameMoved(origin); } if (width != fFrame.Width() || height != fFrame.Height()) { // TODO: remove code duplicatation with // B_WINDOW_RESIZED case... //printf("window size was not up to date\n"); fFrame.right = fFrame.left + width; fFrame.bottom = fFrame.top + height; _AdoptResize(); FrameResized(width, height); } // read culmulated update rect (is in screen coords) fLink->Read(&updateRect); // read tokens for views that need to be drawn // NOTE: we need to read the tokens completely // first, or other calls would likely mess up the // data in the link. BList tokens(20); int32 token; status_t error = fLink->Read(&token); while (error >= B_OK && token != B_NULL_TOKEN) { tokens.AddItem((void*)token); error = fLink->Read(&token); } // draw int32 count = tokens.CountItems(); for (int32 i = 0; i < count; i++) { if (BView* view = _FindView((int32)tokens.ItemAtFast(i))) view->_Draw(updateRect); } // TODO: the tokens are actually hirachically sorted, // so traversing the list in revers and calling // child->DrawAfterChildren would actually work correctly, // only that drawing outside a view is not yet supported // in the app_server. } fLink->StartMessage(AS_END_UPDATE); fLink->Flush(); fInTransaction = false; //printf("BWindow(%s) - UPDATE took %lld usecs\n", Title(), system_time() - now); break; } case _MENUS_DONE_: MenusEnded(); break; // These two are obviously some kind of old scripting messages // this is NOT an app_server message and we have to be cautious case B_WINDOW_MOVE_BY: { BPoint offset; if (msg->FindPoint("data", &offset) == B_OK) MoveBy(offset.x, offset.y); else msg->SendReply(B_MESSAGE_NOT_UNDERSTOOD); break; } // this is NOT an app_server message and we have to be cautious case B_WINDOW_MOVE_TO: { BPoint origin; if (msg->FindPoint("data", &origin) == B_OK) MoveTo(origin); else msg->SendReply(B_MESSAGE_NOT_UNDERSTOOD); break; } case _MESSAGE_DROPPED_: { if (fLastMouseMovedView) target = fLastMouseMovedView; uint32 originalWhat; if (msg->FindInt32("_original_what", (int32*)&originalWhat) == B_OK) { msg->what = originalWhat; msg->RemoveName("_original_what"); } BLooper::DispatchMessage(msg, target); break; } default: BLooper::DispatchMessage(msg, target); break; } } void BWindow::FrameMoved(BPoint new_position) { // does nothing // Hook function } void BWindow::FrameResized(float new_width, float new_height) { // does nothing // Hook function } void BWindow::WorkspacesChanged(uint32 old_ws, uint32 new_ws) { // does nothing // Hook function } void BWindow::WorkspaceActivated(int32 ws, bool state) { // does nothing // Hook function } void BWindow::MenusBeginning() { // does nothing // Hook function } void BWindow::MenusEnded() { // does nothing // Hook function } void BWindow::SetSizeLimits(float minWidth, float maxWidth, float minHeight, float maxHeight) { if (minWidth > maxWidth || minHeight > maxHeight) return; if (Lock()) { fLink->StartMessage(AS_SET_SIZE_LIMITS); fLink->Attach(minWidth); fLink->Attach(maxWidth); fLink->Attach(minHeight); fLink->Attach(maxHeight); int32 code; if (fLink->FlushWithReply(code) == B_OK && code == B_OK) { // read the values that were really enforced on // the server side (the window frame could have // been changed, too) fLink->Read(&fFrame); fLink->Read(&fMinWidth); fLink->Read(&fMaxWidth); fLink->Read(&fMinHeight); fLink->Read(&fMaxHeight); _AdoptResize(); // TODO: the same has to be done for SetLook() (that can alter // the size limits, and hence, the size of the window } Unlock(); } } void BWindow::GetSizeLimits(float *minWidth, float *maxWidth, float *minHeight, float *maxHeight) { // TODO: What about locking?!? *minHeight = fMinHeight; *minWidth = fMinWidth; *maxHeight = fMaxHeight; *maxWidth = fMaxWidth; } void BWindow::SetZoomLimits(float maxWidth, float maxHeight) { // TODO: What about locking?!? if (maxWidth > fMaxWidth) maxWidth = fMaxWidth; else fMaxZoomWidth = maxWidth; if (maxHeight > fMaxHeight) maxHeight = fMaxHeight; else fMaxZoomHeight = maxHeight; } void BWindow::Zoom(BPoint leftTop, float width, float height) { // the default implementation of this hook function // just does the obvious: MoveTo(leftTop); ResizeTo(width, height); } void BWindow::Zoom() { // TODO: What about locking?!? /* from BeBook: However, if the window's rectangle already matches these "zoom" dimensions (give or take a few pixels), Zoom() passes the window's previous ("non-zoomed") size and location. (??????) */ /* From BeBook: The dimensions that non-virtual Zoom() passes to hook Zoom() are deduced from the smallest of three rectangles: */ // TODO: make more elaborate (figure out this window's // tab height and border width... maybe ask app_server) float borderWidth = 5.0; float tabHeight = 26.0; // 1) the rectangle defined by SetZoomLimits(), float zoomedWidth = fMaxZoomWidth; float zoomedHeight = fMaxZoomHeight; // 2) the rectangle defined by SetSizeLimits() if (fMaxWidth < zoomedWidth) zoomedWidth = fMaxWidth; if (fMaxHeight < zoomedHeight) zoomedHeight = fMaxHeight; // 3) the screen rectangle BScreen screen(this); float screenWidth = screen.Frame().Width() - 2 * borderWidth; float screenHeight = screen.Frame().Height() - (borderWidth + tabHeight); if (screenWidth < zoomedWidth) zoomedWidth = screenWidth; if (screenHeight < zoomedHeight) zoomedHeight = screenHeight; BPoint zoomedLeftTop = screen.Frame().LeftTop() + BPoint(borderWidth, tabHeight); // UN-ZOOM: if (fPreviousFrame.IsValid() // NOTE: don't check for fFrame.LeftTop() == zoomedLeftTop // -> makes it easier on the user to get a window back into place && fFrame.Width() == zoomedWidth && fFrame.Height() == zoomedHeight) { // already zoomed! Zoom(fPreviousFrame.LeftTop(), fPreviousFrame.Width(), fPreviousFrame.Height()); return; } // ZOOM: // remember fFrame for later "unzooming" fPreviousFrame = fFrame; Zoom(zoomedLeftTop, zoomedWidth, zoomedHeight); } void BWindow::ScreenChanged(BRect screen_size, color_space depth) { // Hook function // does nothing } void BWindow::SetPulseRate(bigtime_t rate) { // TODO: What about locking?!? if (rate < 0 || rate == fPulseRate) return; fPulseRate = rate; if (rate > 0) { if (fPulseRunner == NULL) { BMessage message(B_PULSE); fPulseRunner = new BMessageRunner(BMessenger(this), &message, rate); } else { fPulseRunner->SetInterval(rate); } } else { // rate == 0 delete fPulseRunner; fPulseRunner = NULL; } } bigtime_t BWindow::PulseRate() const { // TODO: What about locking?!? return fPulseRate; } void BWindow::AddShortcut(uint32 key, uint32 modifiers, BMenuItem *item) { Shortcut* shortcut = new Shortcut(key, modifiers, item); // removes the shortcut if it already exists! RemoveShortcut(key, modifiers); fShortcuts.AddItem(shortcut); } void BWindow::AddShortcut(uint32 key, uint32 modifiers, BMessage *message) { AddShortcut(key, modifiers, message, this); } void BWindow::AddShortcut(uint32 key, uint32 modifiers, BMessage *message, BHandler *target) { if (message == NULL) return; Shortcut* shortcut = new Shortcut(key, modifiers, message, target); // removes the shortcut if it already exists! RemoveShortcut(key, modifiers); fShortcuts.AddItem(shortcut); } void BWindow::RemoveShortcut(uint32 key, uint32 modifiers) { Shortcut* shortcut = _FindShortcut(key, modifiers); if (shortcut != NULL) { fShortcuts.RemoveItem(shortcut); delete shortcut; } else if ((key == 'q' || key == 'Q') && modifiers == B_COMMAND_KEY) { // the quit shortcut is a fake shortcut fNoQuitShortcut = true; } } BButton * BWindow::DefaultButton() const { // TODO: What about locking?!? return fDefaultButton; } void BWindow::SetDefaultButton(BButton *button) { // TODO: What about locking?!? if (fDefaultButton == button) return; if (fDefaultButton != NULL) { // tell old button it's no longer the default one BButton *oldDefault = fDefaultButton; oldDefault->MakeDefault(false); oldDefault->Invalidate(); } fDefaultButton = button; if (button != NULL) { // notify new default button fDefaultButton->MakeDefault(true); fDefaultButton->Invalidate(); } } bool BWindow::NeedsUpdate() const { if (!const_cast(this)->Lock()) return false; fLink->StartMessage(AS_NEEDS_UPDATE); int32 code = B_ERROR; fLink->FlushWithReply(code); const_cast(this)->Unlock(); return code == B_OK; } void BWindow::UpdateIfNeeded() { // works only from the window thread if (find_thread(NULL) != Thread()) return; // if the queue is already locked we are called recursivly // from our own dispatched update message if (((const BMessageQueue *)MessageQueue())->IsLocked()) return; // make sure all requests that would cause an update have // arrived at the server Sync(); // Since we're blocking the event loop, we need to retrieve // all messages that are pending on the port. _DequeueAll(); BMessageQueue *queue = MessageQueue(); queue->Lock(); // First process and remove any _UPDATE_ message in the queue // With the current design, there can only be one at a time BMessage *msg; for (int32 i = 0; (msg = queue->FindMessage(i)) != NULL; i++) { if (msg->what == _UPDATE_) { BWindow::DispatchMessage(msg, this); // we need to make sure that no overridden method is called // here; for BWindow::DispatchMessage() we now exactly what // will happen queue->RemoveMessage(msg); delete msg; break; // NOTE: "i" would have to be decreased if there were // multiple _UPDATE_ messages and we would not break! } } queue->Unlock(); } BView * BWindow::FindView(const char *viewName) const { BAutolock _(const_cast(this)); return fTopView->FindView(viewName); } BView * BWindow::FindView(BPoint point) const { BAutolock _(const_cast(this)); return _FindView(fTopView, point); } BView *BWindow::CurrentFocus() const { return fFocus; } void BWindow::Activate(bool active) { if (!Lock()) return; if (!IsHidden()) { fLink->StartMessage(AS_ACTIVATE_WINDOW); fLink->Attach(active); fLink->Flush(); } Unlock(); } void BWindow::WindowActivated(bool state) { // hook function // does nothing } void BWindow::ConvertToScreen(BPoint *point) const { point->x += fFrame.left; point->y += fFrame.top; } BPoint BWindow::ConvertToScreen(BPoint point) const { return point + fFrame.LeftTop(); } void BWindow::ConvertFromScreen(BPoint *point) const { point->x -= fFrame.left; point->y -= fFrame.top; } BPoint BWindow::ConvertFromScreen(BPoint point) const { return point - fFrame.LeftTop(); } void BWindow::ConvertToScreen(BRect *rect) const { rect->OffsetBy(fFrame.LeftTop()); } BRect BWindow::ConvertToScreen(BRect rect) const { return rect.OffsetByCopy(fFrame.LeftTop()); } void BWindow::ConvertFromScreen(BRect* rect) const { rect->OffsetBy(-fFrame.left, -fFrame.top); } BRect BWindow::ConvertFromScreen(BRect rect) const { return rect.OffsetByCopy(-fFrame.left, -fFrame.top); } bool BWindow::IsMinimized() const { // Hiding takes precendence over minimization!!! if (IsHidden()) return false; return fMinimized; } BRect BWindow::Bounds() const { return BRect(0, 0, fFrame.Width(), fFrame.Height()); } BRect BWindow::Frame() const { return fFrame; } const char * BWindow::Title() const { return fTitle; } void BWindow::SetTitle(const char *title) { if (title == NULL) title = ""; free(fTitle); fTitle = strdup(title); // we will change BWindow's thread name to "w>window title" char threadName[B_OS_NAME_LENGTH]; strcpy(threadName, "w>"); #ifdef __HAIKU__ strlcat(threadName, title, B_OS_NAME_LENGTH); #else int32 length = strlen(title); length = min_c(length, B_OS_NAME_LENGTH - 3); memcpy(threadName + 2, title, length); threadName[length + 2] = '\0'; #endif // change the handler's name SetName(threadName); // if the message loop has been started... if (Thread() >= B_OK) rename_thread(Thread(), threadName); // we notify the app_server so we can actually see the change if (Lock()) { fLink->StartMessage(AS_SET_WINDOW_TITLE); fLink->AttachString(fTitle); fLink->Flush(); Unlock(); } } bool BWindow::IsActive() const { return fActive; } void BWindow::SetKeyMenuBar(BMenuBar *bar) { fKeyMenuBar = bar; } BMenuBar * BWindow::KeyMenuBar() const { return fKeyMenuBar; } bool BWindow::IsModal() const { return fFeel == B_MODAL_SUBSET_WINDOW_FEEL || fFeel == B_MODAL_APP_WINDOW_FEEL || fFeel == B_MODAL_ALL_WINDOW_FEEL; } bool BWindow::IsFloating() const { return fFeel == B_FLOATING_SUBSET_WINDOW_FEEL || fFeel == B_FLOATING_APP_WINDOW_FEEL || fFeel == B_FLOATING_ALL_WINDOW_FEEL; } status_t BWindow::AddToSubset(BWindow *window) { if (window == NULL || window->Feel() != B_NORMAL_WINDOW_FEEL || (fFeel != B_MODAL_SUBSET_WINDOW_FEEL && fFeel != B_FLOATING_SUBSET_WINDOW_FEEL)) return B_BAD_VALUE; if (!Lock()) return B_ERROR; status_t status = B_ERROR; fLink->StartMessage(AS_ADD_TO_SUBSET); fLink->Attach(_get_object_token_(window)); fLink->FlushWithReply(status); Unlock(); return status; } status_t BWindow::RemoveFromSubset(BWindow *window) { if (window == NULL || window->Feel() != B_NORMAL_WINDOW_FEEL || (fFeel != B_MODAL_SUBSET_WINDOW_FEEL && fFeel != B_FLOATING_SUBSET_WINDOW_FEEL)) return B_BAD_VALUE; if (!Lock()) return B_ERROR; status_t status = B_ERROR; fLink->StartMessage(AS_REMOVE_FROM_SUBSET); fLink->Attach(_get_object_token_(window)); fLink->FlushWithReply(status); Unlock(); return status; } status_t BWindow::Perform(perform_code d, void *arg) { return BLooper::Perform(d, arg); } status_t BWindow::SetType(window_type type) { window_look look; window_feel feel; _DecomposeType(type, &look, &feel); status_t status = SetLook(look); if (status == B_OK) status = SetFeel(feel); return status; } window_type BWindow::Type() const { return _ComposeType(fLook, fFeel); } status_t BWindow::SetLook(window_look look) { BAutolock locker(this); fLink->StartMessage(AS_SET_LOOK); fLink->Attach((int32)look); status_t status = B_ERROR; if (fLink->FlushWithReply(status) == B_OK && status == B_OK) fLook = look; // TODO: this could have changed the window size, and thus, we // need to get it from the server (and call _AdoptResize()). return status; } window_look BWindow::Look() const { return fLook; } status_t BWindow::SetFeel(window_feel feel) { BAutolock locker(this); fLink->StartMessage(AS_SET_FEEL); fLink->Attach((int32)feel); status_t status = B_ERROR; if (fLink->FlushWithReply(status) == B_OK && status == B_OK) fFeel = feel; return status; } window_feel BWindow::Feel() const { return fFeel; } status_t BWindow::SetFlags(uint32 flags) { BAutolock locker(this); fLink->StartMessage(AS_SET_FLAGS); fLink->Attach(flags); int32 status = B_ERROR; if (fLink->FlushWithReply(status) == B_OK && status == B_OK) fFlags = flags; return status; } uint32 BWindow::Flags() const { return fFlags; } status_t BWindow::SetWindowAlignment(window_alignment mode, int32 h, int32 hOffset, int32 width, int32 widthOffset, int32 v, int32 vOffset, int32 height, int32 heightOffset) { if ((mode & (B_BYTE_ALIGNMENT | B_PIXEL_ALIGNMENT)) == 0 || (hOffset >= 0 && hOffset <= h) || (vOffset >= 0 && vOffset <= v) || (widthOffset >= 0 && widthOffset <= width) || (heightOffset >= 0 && heightOffset <= height)) return B_BAD_VALUE; // TODO: test if hOffset = 0 and set it to 1 if true. if (!Lock()) return B_ERROR; fLink->StartMessage(AS_SET_ALIGNMENT); fLink->Attach((int32)mode); fLink->Attach(h); fLink->Attach(hOffset); fLink->Attach(width); fLink->Attach(widthOffset); fLink->Attach(v); fLink->Attach(vOffset); fLink->Attach(height); fLink->Attach(heightOffset); status_t status = B_ERROR; fLink->FlushWithReply(status); Unlock(); return status; } status_t BWindow::GetWindowAlignment(window_alignment *mode, int32 *h, int32 *hOffset, int32 *width, int32 *widthOffset, int32 *v, int32 *vOffset, int32 *height, int32 *heightOffset) const { if (!const_cast(this)->Lock()) return B_ERROR; fLink->StartMessage(AS_GET_ALIGNMENT); status_t status; if (fLink->FlushWithReply(status) == B_OK && status == B_OK) { fLink->Read((int32 *)mode); fLink->Read(h); fLink->Read(hOffset); fLink->Read(width); fLink->Read(widthOffset); fLink->Read(v); fLink->Read(hOffset); fLink->Read(height); fLink->Read(heightOffset); } const_cast(this)->Unlock(); return status; } uint32 BWindow::Workspaces() const { if (!const_cast(this)->Lock()) return 0; uint32 workspaces = 0; fLink->StartMessage(AS_GET_WORKSPACES); status_t status; if (fLink->FlushWithReply(status) == B_OK && status == B_OK) fLink->Read(&workspaces); const_cast(this)->Unlock(); return workspaces; } void BWindow::SetWorkspaces(uint32 workspaces) { // TODO: don't forget about Tracker's background window. if (fFeel != B_NORMAL_WINDOW_FEEL) return; if (Lock()) { fLink->StartMessage(AS_SET_WORKSPACES); fLink->Attach(workspaces); fLink->Flush(); Unlock(); } } BView * BWindow::LastMouseMovedView() const { return fLastMouseMovedView; } void BWindow::MoveBy(float dx, float dy) { if ((dx == 0.0 && dy == 0.0) || !Lock()) return; fLink->StartMessage(AS_WINDOW_MOVE); fLink->Attach(dx); fLink->Attach(dy); status_t status; if (fLink->FlushWithReply(status) == B_OK && status == B_OK) fFrame.OffsetBy(dx, dy); Unlock(); } void BWindow::MoveTo(BPoint point) { if (!Lock()) return; point.x = roundf(point.x); point.y = roundf(point.y); if (fFrame.left != point.x || fFrame.top != point.y) { float xOffset = point.x - fFrame.left; float yOffset = point.y - fFrame.top; MoveBy(xOffset, yOffset); } Unlock(); } void BWindow::MoveTo(float x, float y) { MoveTo(BPoint(x, y)); } void BWindow::ResizeBy(float dx, float dy) { if (!Lock()) return; dx = roundf(dx); dy = roundf(dy); // stay in minimum & maximum frame limits if (fFrame.Width() + dx < fMinWidth) dx = fMinWidth - fFrame.Width(); if (fFrame.Width() + dx > fMaxWidth) dx = fMaxWidth - fFrame.Width(); if (fFrame.Height() + dy < fMinHeight) dy = fMinHeight - fFrame.Height(); if (fFrame.Height() + dy > fMaxHeight) dy = fMaxHeight - fFrame.Height(); if (dx != 0.0 || dy != 0.0) { fLink->StartMessage(AS_WINDOW_RESIZE); fLink->Attach(dx); fLink->Attach(dy); status_t status; if (fLink->FlushWithReply(status) == B_OK && status == B_OK) { fFrame.SetRightBottom(fFrame.RightBottom() + BPoint(dx, dy)); _AdoptResize(); } } Unlock(); } void BWindow::ResizeTo(float width, float height) { if (Lock()) { ResizeBy(width - fFrame.Width(), height - fFrame.Height()); Unlock(); } } void BWindow::Show() { if (!fRunCalled) { // this is the fist time Show() is called, which implicetly runs the looper if (fLink->SenderPort() < B_OK) { // We don't have valid app_server connection; there is no point // in starting our looper fTaskID = B_ERROR; return; } else Run(); } if (Lock()) { fShowLevel++; if (fShowLevel == 1) { STRACE(("BWindow(%s): sending AS_SHOW_WINDOW message...\n", Name())); fLink->StartMessage(AS_SHOW_WINDOW); fLink->Flush(); } Unlock(); } } void BWindow::Hide() { if (!Lock()) return; if (--fShowLevel == 0) { fLink->StartMessage(AS_HIDE_WINDOW); fLink->Flush(); } Unlock(); } bool BWindow::IsHidden() const { return fShowLevel <= 0; } bool BWindow::QuitRequested() { return BLooper::QuitRequested(); } thread_id BWindow::Run() { return BLooper::Run(); } status_t BWindow::GetSupportedSuites(BMessage *data) { if (data == NULL) return B_BAD_VALUE; status_t status = data->AddString("Suites", "suite/vnd.Be-window"); if (status == B_OK) { BPropertyInfo propertyInfo(sWindowPropInfo); status = data->AddFlat("message", &propertyInfo); if (status == B_OK) status = BLooper::GetSupportedSuites(data); } return status; } BHandler * BWindow::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, int32 what, const char *property) { if (msg->what == B_WINDOW_MOVE_BY || msg->what == B_WINDOW_MOVE_TO) return this; BPropertyInfo propertyInfo(sWindowPropInfo); if (propertyInfo.FindMatch(msg, index, specifier, what, property) >= 0) { if (!strcmp(property, "View")) { // we will NOT pop the current specifier return fTopView; } else if (!strcmp(property, "MenuBar")) { if (fKeyMenuBar) { msg->PopSpecifier(); return fKeyMenuBar; } else { BMessage replyMsg(B_MESSAGE_NOT_UNDERSTOOD); replyMsg.AddInt32("error", B_NAME_NOT_FOUND); replyMsg.AddString("message", "This window doesn't have a main MenuBar"); msg->SendReply(&replyMsg); return NULL; } } else return this; } return BLooper::ResolveSpecifier(msg, index, specifier, what, property); } // #pragma mark - Private Methods void BWindow::_InitData(BRect frame, const char* title, window_look look, window_feel feel, uint32 flags, uint32 workspace, int32 bitmapToken) { STRACE(("BWindow::InitData()\n")); if (be_app == NULL) { debugger("You need a valid BApplication object before interacting with the app_server"); return; } frame.left = roundf(frame.left); frame.top = roundf(frame.top); frame.right = roundf(frame.right); frame.bottom = roundf(frame.bottom); fFrame = frame; if (title == NULL) title = ""; fTitle = strdup(title); SetName(title); fFeel = feel; fLook = look; fFlags = flags; fInTransaction = false; fActive = false; fShowLevel = 0; fTopView = NULL; fFocus = NULL; fLastMouseMovedView = NULL; fKeyMenuBar = NULL; fDefaultButton = NULL; // Shortcut 'Q' is handled in _HandleKeyDown() directly, as its message // get sent to the application, and not one of our handlers fNoQuitShortcut = false; if ((fFlags & B_NOT_CLOSABLE) == 0) AddShortcut('W', B_COMMAND_KEY, new BMessage(B_QUIT_REQUESTED)); AddShortcut('X', B_COMMAND_KEY, new BMessage(B_CUT), NULL); AddShortcut('C', B_COMMAND_KEY, new BMessage(B_COPY), NULL); AddShortcut('V', B_COMMAND_KEY, new BMessage(B_PASTE), NULL); AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), NULL); fPulseRate = 0; fPulseRunner = NULL; // TODO: is this correct??? should the thread loop be started??? //SetPulseRate( 500000 ); // TODO: see if you can use 'fViewsNeedPulse' fIsFilePanel = false; // TODO: see WHEN is this used! fMaskActivated = false; // TODO: see WHEN is this used! fWaitingForMenu = false; fMenuSem = -1; fMinimized = false; fMaxZoomHeight = 32768.0; fMaxZoomWidth = 32768.0; fMinHeight = 0.0; fMinWidth = 0.0; fMaxHeight = 32768.0; fMaxWidth = 32768.0; fLastViewToken = B_NULL_TOKEN; // TODO: other initializations! // Create the server-side window port_id receivePort = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, "wIsLocked()) { be_app->Lock(); locked = true; } // let app_server know that a window has been created. fLink = new BPrivate::PortLink( BApplication::Private::ServerLink()->SenderPort(), receivePort); if (bitmapToken < 0) { fLink->StartMessage(AS_CREATE_WINDOW); } else { fLink->StartMessage(AS_CREATE_OFFSCREEN_WINDOW); fLink->Attach(bitmapToken); } fLink->Attach(fFrame); fLink->Attach((uint32)fLook); fLink->Attach((uint32)fFeel); fLink->Attach(fFlags); fLink->Attach(workspace); fLink->Attach(_get_object_token_(this)); fLink->Attach(receivePort); fLink->Attach(fMsgPort); fLink->AttachString(title); port_id sendPort; int32 code; if (fLink->FlushWithReply(code) == B_OK && code == B_OK && fLink->Read(&sendPort) == B_OK) { // read the frame size and its limits that were really // enforced on the server side fLink->Read(&fFrame); fLink->Read(&fMinWidth); fLink->Read(&fMaxWidth); fLink->Read(&fMinHeight); fLink->Read(&fMaxHeight); fMaxZoomWidth = fMaxWidth; fMaxZoomHeight = fMaxHeight; } else sendPort = -1; // Redirect our link to the new window connection fLink->SetSenderPort(sendPort); if (locked) be_app->Unlock(); STRACE(("Server says that our send port is %ld\n", sendPort)); STRACE(("Window locked?: %s\n", IsLocked() ? "True" : "False")); _CreateTopView(); } //! Reads all pending messages from the window port and put them into the queue. void BWindow::_DequeueAll() { // Get message count from port int32 count = port_count(fMsgPort); for (int32 i = 0; i < count; i++) { BMessage *message = MessageFromPort(0); if (message != NULL) fQueue->AddMessage(message); } } /*! This here is an almost complete code duplication to BLooper::task_looper() but with some important differences: a) it uses the _DetermineTarget() method to tell what the later target of a message will be, if no explicit target is supplied. b) it calls _UnpackMessage() and _SanitizeMessage() to duplicate the message to all of its intended targets, and to add all fields the target would expect in such a message. This is important because the app_server sends all input events to the preferred handler, and expects them to be correctly distributed to their intended targets. */ void BWindow::task_looper() { STRACE(("info: BWindow::task_looper() started.\n")); // Check that looper is locked (should be) AssertLocked(); Unlock(); if (IsLocked()) debugger("window must not be locked!"); // loop: As long as we are not terminating. while (!fTerminating) { // TODO: timeout determination algo // Read from message port (how do we determine what the timeout is?) BMessage* msg = MessageFromPort(); // Did we get a message? if (msg) { // Add to queue fQueue->AddMessage(msg); } else continue; // Get message count from port int32 msgCount = port_count(fMsgPort); for (int32 i = 0; i < msgCount; ++i) { // Read 'count' messages from port (so we will not block) // We use zero as our timeout since we know there is stuff there msg = MessageFromPort(0); // Add messages to queue if (msg) fQueue->AddMessage(msg); } // loop: As long as there are messages in the queue and the port is // empty... and we are not terminating, of course. bool dispatchNextMessage = true; while (!fTerminating && dispatchNextMessage) { // Get next message from queue (assign to fLastMessage) fLastMessage = fQueue->NextMessage(); // Lock the looper if (!Lock()) break; if (!fLastMessage) { // No more messages: Unlock the looper and terminate the // dispatch loop. dispatchNextMessage = false; } else { // Get the target handler BMessage::Private messagePrivate(fLastMessage); bool usePreferred = messagePrivate.UsePreferredTarget(); BHandler *handler = NULL; bool dropMessage = false; if (usePreferred) { handler = PreferredHandler(); if (handler == NULL) handler = this; } else { gDefaultTokens.GetToken(messagePrivate.GetTarget(), B_HANDLER_TOKEN, (void **)&handler); // if this handler doesn't belong to us, we drop the message if (handler != NULL && handler->Looper() != this) { dropMessage = true; handler = NULL; } } if ((handler == NULL && !dropMessage) || usePreferred) handler = _DetermineTarget(fLastMessage, handler); unpack_cookie cookie; while (_UnpackMessage(cookie, &fLastMessage, &handler, &usePreferred)) { // if there is no target handler, the message is dropped if (handler != NULL) { // Is this a scripting message? if (fLastMessage->HasSpecifiers()) { int32 index = 0; // Make sure the current specifier is kosher if (fLastMessage->GetCurrentSpecifier(&index) == B_OK) handler = resolve_specifier(handler, fLastMessage); } if (handler != NULL) handler = _TopLevelFilter(fLastMessage, handler); if (handler != NULL) { _SanitizeMessage(fLastMessage, handler, usePreferred); DispatchMessage(fLastMessage, handler); } } // Delete the current message delete fLastMessage; fLastMessage = NULL; } } if (fTerminating) { // we leave the looper locked when we quit return; } Unlock(); // Are any messages on the port? if (port_count(fMsgPort) > 0) { // Do outer loop dispatchNextMessage = false; } } } } window_type BWindow::_ComposeType(window_look look, window_feel feel) const { switch (feel) { case B_NORMAL_WINDOW_FEEL: switch (look) { case B_TITLED_WINDOW_LOOK: return B_TITLED_WINDOW; case B_DOCUMENT_WINDOW_LOOK: return B_DOCUMENT_WINDOW; case B_BORDERED_WINDOW_LOOK: return B_BORDERED_WINDOW; default: return B_UNTYPED_WINDOW; } break; case B_MODAL_APP_WINDOW_FEEL: if (look == B_MODAL_WINDOW_LOOK) return B_MODAL_WINDOW; break; case B_FLOATING_APP_WINDOW_FEEL: if (look == B_FLOATING_WINDOW_LOOK) return B_FLOATING_WINDOW; break; default: return B_UNTYPED_WINDOW; } return B_UNTYPED_WINDOW; } void BWindow::_DecomposeType(window_type type, window_look *_look, window_feel *_feel) const { switch (type) { case B_DOCUMENT_WINDOW: *_look = B_DOCUMENT_WINDOW_LOOK; *_feel = B_NORMAL_WINDOW_FEEL; break; case B_MODAL_WINDOW: *_look = B_MODAL_WINDOW_LOOK; *_feel = B_MODAL_APP_WINDOW_FEEL; break; case B_FLOATING_WINDOW: *_look = B_FLOATING_WINDOW_LOOK; *_feel = B_FLOATING_APP_WINDOW_FEEL; break; case B_BORDERED_WINDOW: *_look = B_BORDERED_WINDOW_LOOK; *_feel = B_NORMAL_WINDOW_FEEL; break; case B_TITLED_WINDOW: case B_UNTYPED_WINDOW: default: *_look = B_TITLED_WINDOW_LOOK; *_feel = B_NORMAL_WINDOW_FEEL; break; } } void BWindow::_CreateTopView() { STRACE(("_CreateTopView(): enter\n")); BRect frame = fFrame.OffsetToCopy(B_ORIGIN); fTopView = new BView(frame, "fTopView", B_FOLLOW_ALL, B_WILL_DRAW); fTopView->fTopLevelView = true; //inhibit check_lock() fLastViewToken = _get_object_token_(fTopView); // set fTopView's owner, add it to window's eligible handler list // and also set its next handler to be this window. STRACE(("Calling setowner fTopView = %p this = %p.\n", fTopView, this)); fTopView->_SetOwner(this); // we can't use AddChild() because this is the top view fTopView->_CreateSelf(); STRACE(("BuildTopView ended\n")); } /*! Resizes the top view to match the window size. This will also adapt the size of all its child views as needed. This method has to be called whenever the frame of the window changes. */ void BWindow::_AdoptResize() { // Resize views according to their resize modes - this // saves us some server communication, as the server // does the same with our views on its side. int32 deltaWidth = (int32)(fFrame.Width() - fTopView->Bounds().Width()); int32 deltaHeight = (int32)(fFrame.Height() - fTopView->Bounds().Height()); if (deltaWidth == 0 && deltaHeight == 0) return; fTopView->_ResizeBy(deltaWidth, deltaHeight); } void BWindow::_SetFocus(BView *focusView, bool notifyInputServer) { if (fFocus == focusView) return; if (focusView) focusView->MakeFocus(true); // we notify the input server if we are passing focus // from a view which has the B_INPUT_METHOD_AWARE to a one // which does not, or vice-versa if (notifyInputServer) { bool oldIMAware = false, newIMAware = false; if (focusView) newIMAware = focusView->Flags() & B_INPUT_METHOD_AWARE; if (fFocus) oldIMAware = fFocus->Flags() & B_INPUT_METHOD_AWARE; if (newIMAware ^ oldIMAware) { BMessage msg(newIMAware ? IS_FOCUS_IM_AWARE_VIEW : IS_UNFOCUS_IM_AWARE_VIEW); BMessage reply; _control_input_server_(&msg, &reply); // do we care return code ? } } fFocus = focusView; } /*! \brief Determines the target of a message received for the focus view. */ BHandler * BWindow::_DetermineTarget(BMessage *message, BHandler *target) { if (target == NULL) target = this; switch (message->what) { case B_KEY_DOWN: case B_KEY_UP: { // if we have a default button, it might want to hear // about pressing the key int32 rawChar; if (DefaultButton() != NULL && message->FindInt32("raw_char", &rawChar) == B_OK && rawChar == B_ENTER) return DefaultButton(); // supposed to fall through } case B_UNMAPPED_KEY_DOWN: case B_UNMAPPED_KEY_UP: case B_MODIFIERS_CHANGED: // these messages should be dispatched by the focus view if (CurrentFocus() != NULL) return CurrentFocus(); break; case B_MOUSE_DOWN: case B_MOUSE_UP: case B_MOUSE_MOVED: case B_MOUSE_WHEEL_CHANGED: // is there a token of the view that is currently under the mouse? int32 token; if (message->FindInt32("_view_token", &token) == B_OK) { BView* view = _FindView(token); if (view != NULL) return view; } // if there is no valid token in the message, we try our // luck with the last target, if available if (fLastMouseMovedView != NULL) return fLastMouseMovedView; break; case B_PULSE: case B_QUIT_REQUESTED: // TODO: test wether R5 will let BView dispatch these messages return this; default: break; } return target; } /*! \brief Distributes the message to its intended targets. This is done for all messages that should go to the preferred handler. Returns \c true in case the message should still be dispatched */ bool BWindow::_UnpackMessage(unpack_cookie& cookie, BMessage** _message, BHandler** _target, bool* _usePreferred) { if (cookie.message == NULL) return false; if (cookie.index == 0 && !cookie.tokens_scanned) { if (!*_usePreferred) { // we only consider messages targeted at the preferred handler cookie.message = NULL; return true; } // initialize our cookie cookie.message = *_message; cookie.focus = *_target; if (cookie.focus != NULL) cookie.focus_token = _get_object_token_(*_target); if (fLastMouseMovedView != NULL && cookie.message->what == B_MOUSE_MOVED) cookie.last_view_token = _get_object_token_(fLastMouseMovedView); *_usePreferred = false; } _DequeueAll(); // distribute the message to all targets specified in the // message directly (but not to the focus view) for (int32 token; !cookie.tokens_scanned && cookie.message->FindInt32("_token", cookie.index, &token) == B_OK; cookie.index++) { // focus view is preferred and should get its message directly if (token == cookie.focus_token) { cookie.found_focus = true; continue; } if (token == cookie.last_view_token) continue; BView* target = _FindView(token); if (target == NULL) continue; *_message = new BMessage(*cookie.message); *_target = target; cookie.index++; return true; } cookie.tokens_scanned = true; // if there is a last mouse moved view, and the new focus is // different, the previous view wants to get its B_EXITED_VIEW // message if (cookie.last_view_token != B_NULL_TOKEN && fLastMouseMovedView != NULL && fLastMouseMovedView != cookie.focus) { *_message = new BMessage(*cookie.message); *_target = fLastMouseMovedView; cookie.last_view_token = B_NULL_TOKEN; return true; } bool dispatchToFocus = true; // check if the focus token is still valid (could have been removed in the mean time) BHandler* handler; if (gDefaultTokens.GetToken(cookie.focus_token, B_HANDLER_TOKEN, (void**)&handler) != B_OK || handler->Looper() != this) dispatchToFocus = false; if (dispatchToFocus && cookie.index > 0) { // should this message still be dispatched by the focus view? bool feedFocus; if (!cookie.found_focus && (cookie.message->FindBool("_feed_focus", &feedFocus) != B_OK || feedFocus == false)) dispatchToFocus = false; } if (!dispatchToFocus) { delete cookie.message; cookie.message = NULL; return false; } *_message = cookie.message; *_target = cookie.focus; *_usePreferred = true; cookie.message = NULL; return true; } void BWindow::_SanitizeMessage(BMessage* message, BHandler* target, bool usePreferred) { if (target == NULL) return; switch (message->what) { case B_MOUSE_MOVED: case B_MOUSE_UP: case B_MOUSE_DOWN: BPoint where; if (message->FindPoint("screen_where", &where) != B_OK) break; // add local window coordinates message->AddPoint("where", ConvertFromScreen(where)); BView* view = dynamic_cast(target); if (view != NULL) { // add local view coordinates message->AddPoint("be:view_where", view->ConvertFromScreen(where)); if (message->what == B_MOUSE_MOVED) { // is there a token of the view that is currently under the mouse? BView* viewUnderMouse = NULL; int32 token; if (message->FindInt32("_view_token", &token) == B_OK) viewUnderMouse = _FindView(token); // add transit information int32 transit; if (viewUnderMouse == view) { // the mouse is over the target view if (fLastMouseMovedView != view) transit = B_ENTERED_VIEW; else transit = B_INSIDE_VIEW; } else { // the mouse is not over the target view if (view == fLastMouseMovedView) transit = B_EXITED_VIEW; else transit = B_OUTSIDE_VIEW; } message->AddInt32("be:transit", transit); if (usePreferred || viewUnderMouse == NULL) fLastMouseMovedView = viewUnderMouse; } } break; } } bool BWindow::_HandleKeyDown(char key, uint32 modifiers) { // TODO: ask people if using 'raw_char' is OK ? // handle BMenuBar key if (key == B_ESCAPE && (modifiers & B_COMMAND_KEY) != 0 && fKeyMenuBar) { // TODO: ask Marc about 'fWaitingForMenu' member! // fWaitingForMenu = true; fKeyMenuBar->StartMenuBar(0, true, false, NULL); return true; } // Keyboard navigation through views // (B_OPTION_KEY makes BTextViews and friends navigable, even in editing mode) if (key == B_TAB && (modifiers & (B_COMMAND_KEY | B_OPTION_KEY)) != 0) { _KeyboardNavigation(); return true; } // Deskbar's Switcher if (key == B_TAB && (modifiers & B_CONTROL_KEY) != 0) { BMessenger deskbar(kDeskbarSignature); if (deskbar.IsValid()) { BMessage message('TASK'); message.AddInt32("key", B_TAB); message.AddInt32("modifiers", modifiers); message.AddInt64("when", system_time()); message.AddInt32("team", Team()); deskbar.SendMessage(&message); } return true; } // Handle shortcuts if ((modifiers & B_COMMAND_KEY) != 0) { // Command+q has been pressed, so, we will quit // the shortcut mechanism doesn't allow handlers outside the window if (!fNoQuitShortcut && (key == 'Q' || key == 'q')) { BMessage message(B_QUIT_REQUESTED); message.AddBool("shortcut", true); be_app->PostMessage(&message); return true; } Shortcut* shortcut = _FindShortcut(key, modifiers); if (shortcut != NULL) { // TODO: would be nice to move this functionality to // a Shortcut::Invoke() method - but since BMenu::InvokeItem() // (and BMenuItem::Invoke()) are private, I didn't want // to mess with them (BMenuItem::Invoke() is public in // Dano/Zeta, though, maybe we should just follow their // example) if (shortcut->MenuItem() != NULL) { BMenu* menu = shortcut->MenuItem()->Menu(); if (menu != NULL) menu->InvokeItem(shortcut->MenuItem(), true); } else { BHandler* target = shortcut->Target(); if (target == NULL) target = CurrentFocus(); if (shortcut->Message() != NULL) { BMessage message(*shortcut->Message()); if (message.ReplaceInt64("when", system_time()) != B_OK) message.AddInt64("when", system_time()); if (message.ReplaceBool("shortcut", true) != B_OK) message.AddBool("shortcut", true); PostMessage(&message, target); } } return true; } } // TODO: convert keys to the encoding of the target view return false; } void BWindow::_KeyboardNavigation() { BMessage *message = CurrentMessage(); if (message == NULL) return; const char *bytes; uint32 modifiers; if (message->FindString("bytes", &bytes) != B_OK || bytes[0] != B_TAB) return; message->FindInt32("modifiers", (int32*)&modifiers); BView *nextFocus; int32 jumpGroups = modifiers & B_CONTROL_KEY ? B_NAVIGABLE_JUMP : B_NAVIGABLE; if (modifiers & B_SHIFT_KEY) nextFocus = _FindPreviousNavigable(fFocus, jumpGroups); else nextFocus = _FindNextNavigable(fFocus, jumpGroups); if (nextFocus && nextFocus != fFocus) _SetFocus(nextFocus, false); } BMessage * BWindow::ConvertToMessage(void *raw, int32 code) { return BLooper::ConvertToMessage(raw, code); } BWindow::Shortcut * BWindow::_FindShortcut(uint32 key, uint32 modifiers) { int32 count = fShortcuts.CountItems(); key = Shortcut::PrepareKey(key); modifiers = Shortcut::PrepareModifiers(modifiers); for (int32 index = 0; index < count; index++) { Shortcut *shortcut = (Shortcut *)fShortcuts.ItemAt(index); if (shortcut->Matches(key, modifiers)) return shortcut; } return NULL; } BView * BWindow::_FindView(int32 token) { BHandler* handler; if (gDefaultTokens.GetToken(token, B_HANDLER_TOKEN, (void**)&handler) != B_OK) return NULL; // the view must belong to us in order to be found by this method BView* view = dynamic_cast(handler); if (view != NULL && view->Window() == this) return view; return NULL; } BView * BWindow::_FindView(BView *view, BPoint point) const { // TODO: this is totally broken (bounds vs. frame) - since // BView::Bounds() potentially queries the app_server // anyway, we could just let the app_server answer this // query directly. if (view->Bounds().Contains(point) && !view->fFirstChild) return view; BView *child = view->fFirstChild; while (child != NULL) { if ((view = _FindView(child, point)) != NULL) return view; child = child->fNextSibling; } return NULL; } BView * BWindow::_FindNextNavigable(BView* focus, uint32 flags) { if (focus == NULL) focus = fTopView; BView* nextFocus = focus; // Search the tree for views that accept focus (depth search) while (true) { if (nextFocus->fFirstChild) nextFocus = nextFocus->fFirstChild; else if (nextFocus->fNextSibling) nextFocus = nextFocus->fNextSibling; else { // go to the nearest parent with a next sibling while (!nextFocus->fNextSibling && nextFocus->fParent) { nextFocus = nextFocus->fParent; } if (nextFocus == fTopView) { // if we started with the top view, we traversed the whole tree already if (nextFocus == focus) return NULL; nextFocus = nextFocus->fFirstChild; } else nextFocus = nextFocus->fNextSibling; } if (nextFocus == focus || nextFocus == NULL) { // When we get here it means that the hole tree has been // searched and there is no view with B_NAVIGABLE(_JUMP) flag set! return NULL; } if (!nextFocus->IsHidden() && (nextFocus->Flags() & flags) != 0) return nextFocus; } } BView * BWindow::_FindPreviousNavigable(BView* focus, uint32 flags) { if (focus == NULL) focus = fTopView; BView* previousFocus = focus; // Search the tree for the previous view that accept focus while (true) { if (previousFocus->fPreviousSibling) { // find the last child in the previous sibling previousFocus = _LastViewChild(previousFocus->fPreviousSibling); } else { previousFocus = previousFocus->fParent; if (previousFocus == fTopView) previousFocus = _LastViewChild(fTopView); } if (previousFocus == focus || previousFocus == NULL) { // When we get here it means that the hole tree has been // searched and there is no view with B_NAVIGABLE(_JUMP) flag set! return NULL; } if (!previousFocus->IsHidden() && (previousFocus->Flags() & flags) != 0) return previousFocus; } } /*! Returns the last child in a view hierarchy. Needed only by _FindPreviousNavigable(). */ BView * BWindow::_LastViewChild(BView *parent) { while (true) { BView *last = parent->fFirstChild; if (last == NULL) return parent; while (last->fNextSibling) { last = last->fNextSibling; } parent = last; } } void BWindow::SetIsFilePanel(bool isFilePanel) { fIsFilePanel = isFilePanel; } bool BWindow::IsFilePanel() const { return fIsFilePanel; } //------------------------------------------------------------------------------ // Virtual reserved Functions void BWindow::_ReservedWindow1() {} void BWindow::_ReservedWindow2() {} void BWindow::_ReservedWindow3() {} void BWindow::_ReservedWindow4() {} void BWindow::_ReservedWindow5() {} void BWindow::_ReservedWindow6() {} void BWindow::_ReservedWindow7() {} void BWindow::_ReservedWindow8() {} void BWindow::PrintToStream() const { printf("BWindow '%s' data:\ Title = %s\ Token = %ld\ InTransaction = %s\ Active = %s\ fShowLevel = %d\ Flags = %lx\ send_port = %ld\ receive_port = %ld\ fTopView name = %s\ focus view name = %s\ lastMouseMoved = %s\ fLink = %p\ KeyMenuBar name = %s\ DefaultButton = %s\ # of shortcuts = %ld", Name(), fTitle, _get_object_token_(this), fInTransaction == true ? "yes" : "no", fActive == true ? "yes" : "no", fShowLevel, fFlags, fLink->SenderPort(), fLink->ReceiverPort(), fTopView != NULL ? fTopView->Name() : "NULL", fFocus != NULL ? fFocus->Name() : "NULL", fLastMouseMovedView != NULL ? fLastMouseMovedView->Name() : "NULL", fLink, fKeyMenuBar != NULL ? fKeyMenuBar->Name() : "NULL", fDefaultButton != NULL ? fDefaultButton->Name() : "NULL", fShortcuts.CountItems()); /* for( int32 i=0; ikey > 127)?"ASCII":"UNICODE"); key->message->PrintToStream(); } */ printf("\ topViewToken = %ld\ isFilePanel = %s\ MaskActivated = %s\ pulseRate = %lld\ waitingForMenu = %s\ minimized = %s\ Menu semaphore = %ld\ maxZoomHeight = %f\ maxZoomWidth = %f\ minWindHeight = %f\ minWindWidth = %f\ maxWindHeight = %f\ maxWindWidth = %f\ frame = ( %f, %f, %f, %f )\ look = %d\ feel = %d\ lastViewToken = %ld\ pulseRunner = %s\n", fTopViewToken, fIsFilePanel==true?"Yes":"No", fMaskActivated==true?"Yes":"No", fPulseRate, fWaitingForMenu==true?"Yes":"No", fMinimized==true?"Yes":"No", fMenuSem, fMaxZoomHeight, fMaxZoomWidth, fMinHeight, fMinWidth, fMaxHeight, fMaxWidth, fFrame.left, fFrame.top, fFrame.right, fFrame.bottom, (int16)fLook, (int16)fFeel, fLastViewToken, fPulseRunner != NULL ? "In place" : "NULL"); } /* TODO list: *) test arguments for SetWindowAligment *) call hook functions: MenusBeginning, MenusEnded. Add menu activation code. */