/////////////////////////////////////////////////////////////////////// // File: scrollview.cc // Description: ScrollView // Author: Joern Wanke // Created: Thu Nov 29 2007 // // (C) Copyright 2007, Google Inc. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // /////////////////////////////////////////////////////////////////////// // #include #include #include #include #include #include #include #include #include #include // Include automatically generated configuration file if running autoconf. #ifdef HAVE_CONFIG_H #include "config_auto.h" #endif #include "scrollview.h" #ifdef _MSC_VER #pragma warning(disable:4786) // Don't give stupid warnings for stl #pragma warning(disable:4018) // signed/unsigned warnings #pragma warning(disable:4530) // exception warnings #endif const int kSvPort = 8461; const int kMaxMsgSize = 4096; const int kMaxIntPairSize = 45; // Holds %d,%d, for up to 64 bit. #include "svutil.h" #include "allheaders.h" struct SVPolyLineBuffer { bool empty; // Independent indicator to allow SendMsg to call SendPolygon. std::vector xcoords; std::vector ycoords; }; // A map between the window IDs and their corresponding pointers. static std::map svmap; static SVMutex* svmap_mu; // A map of all semaphores waiting for a specific event on a specific window. static std::map, std::pair > waiting_for_events; static SVMutex* waiting_for_events_mu; SVEvent* SVEvent::copy() { SVEvent* any = new SVEvent; any->command_id = command_id; any->counter = counter; any->parameter = new char[strlen(parameter) + 1]; strncpy(any->parameter, parameter, strlen(parameter)); any->parameter[strlen(parameter)] = '\0'; any->type = type; any->x = x; any->y = y; any->x_size = x_size; any->y_size = y_size; any->window = window; return any; } #ifndef GRAPHICS_DISABLED /// This is the main loop which handles the ScrollView-logic from the server /// to the client. It basically loops through messages, parses them to events /// and distributes it to the waiting handlers. /// It is run from a different thread and synchronizes via SVSync. void* ScrollView::MessageReceiver(void* a) { int counter_event_id = 0; // ongoing counter char* message = NULL; // Wait until a new message appears in the input stream_. do { message = ScrollView::GetStream()->Receive(); } while (message == NULL); // This is the main loop which iterates until the server is dead (strlen = -1). // It basically parses for 3 different messagetypes and then distributes the // events accordingly. while (1) { // The new event we create. SVEvent* cur = new SVEvent; // The ID of the corresponding window. int window_id; int ev_type; int n; // Fill the new SVEvent properly. sscanf(message, "%d,%d,%d,%d,%d,%d,%d,%n", &window_id, &ev_type, &cur->x, &cur->y, &cur->x_size, &cur->y_size, &cur->command_id, &n); char* p = (message + n); svmap_mu->Lock(); cur->window = svmap[window_id]; if (cur->window != NULL) { cur->parameter = new char[strlen(p) + 1]; strncpy(cur->parameter, p, strlen(p) + 1); if (strlen(p) > 0) { // remove the last \n cur->parameter[strlen(p)] = '\0'; } cur->type = static_cast(ev_type); // Correct selection coordinates so x,y is the min pt and size is +ve. if (cur->x_size > 0) cur->x -= cur->x_size; else cur->x_size = -cur->x_size; if (cur->y_size > 0) cur->y -= cur->y_size; else cur->y_size = -cur->y_size; // Returned y will be the bottom-left if y is reversed. if (cur->window->y_axis_is_reversed_) cur->y = cur->window->TranslateYCoordinate(cur->y + cur->y_size); cur->counter = counter_event_id; // Increase by 2 since we will also create an SVET_ANY event from cur, // which will have a counter_id of cur + 1 (and thus gets processed // after cur). counter_event_id += 2; // In case of an SVET_EXIT event, quit the whole application. if (ev_type == SVET_EXIT) { ScrollView::Exit(); } // Place two copies of it in the table for the window. cur->window->SetEvent(cur); // Check if any of the threads currently waiting want it. std::pair awaiting_list(cur->window, cur->type); std::pair awaiting_list_any(cur->window, SVET_ANY); std::pair awaiting_list_any_window((ScrollView*)0, SVET_ANY); waiting_for_events_mu->Lock(); if (waiting_for_events.count(awaiting_list) > 0) { waiting_for_events[awaiting_list].second = cur; waiting_for_events[awaiting_list].first->Signal(); } else if (waiting_for_events.count(awaiting_list_any) > 0) { waiting_for_events[awaiting_list_any].second = cur; waiting_for_events[awaiting_list_any].first->Signal(); } else if (waiting_for_events.count(awaiting_list_any_window) > 0) { waiting_for_events[awaiting_list_any_window].second = cur; waiting_for_events[awaiting_list_any_window].first->Signal(); } else { // No one wanted it, so delete it. delete cur; } waiting_for_events_mu->Unlock(); // Signal the corresponding semaphore twice (for both copies). ScrollView* sv = svmap[window_id]; if (sv != NULL) { sv->Signal(); sv->Signal(); } } else { delete cur; // Applied to no window. } svmap_mu->Unlock(); // Wait until a new message appears in the input stream_. do { message = ScrollView::GetStream()->Receive(); } while (message == NULL); } return 0; } // Table to implement the color index values in the old system. int table_colors[ScrollView::GREEN_YELLOW+1][4]= { {0, 0, 0, 0}, // NONE (transparent) {0, 0, 0, 255}, // BLACK. {255, 255, 255, 255}, // WHITE. {255, 0, 0, 255}, // RED. {255, 255, 0, 255}, // YELLOW. {0, 255, 0, 255}, // GREEN. {0, 255, 255, 255}, // CYAN. {0, 0, 255, 255}, // BLUE. {255, 0, 255, 255}, // MAGENTA. {0, 128, 255, 255}, // AQUAMARINE. {0, 0, 64, 255}, // DARK_SLATE_BLUE. {128, 128, 255, 255}, // LIGHT_BLUE. {64, 64, 255, 255}, // MEDIUM_BLUE. {0, 0, 32, 255}, // MIDNIGHT_BLUE. {0, 0, 128, 255}, // NAVY_BLUE. {192, 192, 255, 255}, // SKY_BLUE. {64, 64, 128, 255}, // SLATE_BLUE. {32, 32, 64, 255}, // STEEL_BLUE. {255, 128, 128, 255}, // CORAL. {128, 64, 0, 255}, // BROWN. {128, 128, 0, 255}, // SANDY_BROWN. {192, 192, 0, 255}, // GOLD. {192, 192, 128, 255}, // GOLDENROD. {0, 64, 0, 255}, // DARK_GREEN. {32, 64, 0, 255}, // DARK_OLIVE_GREEN. {64, 128, 0, 255}, // FOREST_GREEN. {128, 255, 0, 255}, // LIME_GREEN. {192, 255, 192, 255}, // PALE_GREEN. {192, 255, 0, 255}, // YELLOW_GREEN. {192, 192, 192, 255}, // LIGHT_GREY. {64, 64, 128, 255}, // DARK_SLATE_GREY. {64, 64, 64, 255}, // DIM_GREY. {128, 128, 128, 255}, // GREY. {64, 192, 0, 255}, // KHAKI. {255, 0, 192, 255}, // MAROON. {255, 128, 0, 255}, // ORANGE. {255, 128, 64, 255}, // ORCHID. {255, 192, 192, 255}, // PINK. {128, 0, 128, 255}, // PLUM. {255, 0, 64, 255}, // INDIAN_RED. {255, 64, 0, 255}, // ORANGE_RED. {255, 0, 192, 255}, // VIOLET_RED. {255, 192, 128, 255}, // SALMON. {128, 128, 0, 255}, // TAN. {0, 255, 255, 255}, // TURQUOISE. {0, 128, 128, 255}, // DARK_TURQUOISE. {192, 0, 255, 255}, // VIOLET. {128, 128, 0, 255}, // WHEAT. {128, 255, 0, 255} // GREEN_YELLOW }; /******************************************************************************* * Scrollview implementation. *******************************************************************************/ SVNetwork* ScrollView::stream_ = NULL; int ScrollView::nr_created_windows_ = 0; int ScrollView::image_index_ = 0; /// Calls Initialize with all arguments given. ScrollView::ScrollView(const char* name, int x_pos, int y_pos, int x_size, int y_size, int x_canvas_size, int y_canvas_size, bool y_axis_reversed, const char* server_name) { Initialize(name, x_pos, y_pos, x_size, y_size, x_canvas_size, y_canvas_size, y_axis_reversed, server_name);} /// Calls Initialize with default argument for server_name_. ScrollView::ScrollView(const char* name, int x_pos, int y_pos, int x_size, int y_size, int x_canvas_size, int y_canvas_size, bool y_axis_reversed) { Initialize(name, x_pos, y_pos, x_size, y_size, x_canvas_size, y_canvas_size, y_axis_reversed, "localhost"); } /// Calls Initialize with default argument for server_name_ & y_axis_reversed. ScrollView::ScrollView(const char* name, int x_pos, int y_pos, int x_size, int y_size, int x_canvas_size, int y_canvas_size) { Initialize(name, x_pos, y_pos, x_size, y_size, x_canvas_size, y_canvas_size, false, "localhost"); } /// Sets up a ScrollView window, depending on the constructor variables. void ScrollView::Initialize(const char* name, int x_pos, int y_pos, int x_size, int y_size, int x_canvas_size, int y_canvas_size, bool y_axis_reversed, const char* server_name) { // If this is the first ScrollView Window which gets created, there is no // network connection yet and we have to set it up in a different thread. if (stream_ == NULL) { nr_created_windows_ = 0; stream_ = new SVNetwork(server_name, kSvPort); waiting_for_events_mu = new SVMutex(); svmap_mu = new SVMutex(); SendRawMessage( "svmain = luajava.bindClass('com.google.scrollview.ScrollView')\n"); SVSync::StartThread(MessageReceiver, NULL); } // Set up the variables on the clientside. nr_created_windows_++; event_handler_ = NULL; event_handler_ended_ = false; y_axis_is_reversed_ = y_axis_reversed; y_size_ = y_canvas_size; window_name_ = name; window_id_ = nr_created_windows_; // Set up polygon buffering. points_ = new SVPolyLineBuffer; points_->empty = true; svmap_mu->Lock(); svmap[window_id_] = this; svmap_mu->Unlock(); for (int i = 0; i < SVET_COUNT; i++) { event_table_[i] = NULL; } mutex_ = new SVMutex(); semaphore_ = new SVSemaphore(); // Set up an actual Window on the client side. char message[kMaxMsgSize]; snprintf(message, sizeof(message), "w%u = luajava.newInstance('com.google.scrollview.ui" ".SVWindow','%s',%u,%u,%u,%u,%u,%u,%u)\n", window_id_, window_name_, window_id_, x_pos, y_pos, x_size, y_size, x_canvas_size, y_canvas_size); SendRawMessage(message); SVSync::StartThread(StartEventHandler, this); } /// Sits and waits for events on this window. void* ScrollView::StartEventHandler(void* a) { ScrollView* sv = reinterpret_cast(a); SVEvent* new_event; do { stream_->Flush(); sv->semaphore_->Wait(); new_event = NULL; int serial = -1; int k = -1; sv->mutex_->Lock(); // Check every table entry if he is is valid and not already processed. for (int i = 0; i < SVET_COUNT; i++) { if (sv->event_table_[i] != NULL && (serial < 0 || sv->event_table_[i]->counter < serial)) { new_event = sv->event_table_[i]; serial = sv->event_table_[i]->counter; k = i; } } // If we didn't find anything we had an old alarm and just sleep again. if (new_event != NULL) { sv->event_table_[k] = NULL; sv->mutex_->Unlock(); if (sv->event_handler_ != NULL) { sv->event_handler_->Notify(new_event); } if (new_event->type == SVET_DESTROY) { // Signal the destructor that it is safe to terminate. sv->event_handler_ended_ = true; sv = NULL; } delete new_event; // Delete the pointer after it has been processed. } else { sv->mutex_->Unlock(); } // The thread should run as long as its associated window is alive. } while (sv != NULL); return 0; } #endif // GRAPHICS_DISABLED ScrollView::~ScrollView() { #ifndef GRAPHICS_DISABLED svmap_mu->Lock(); if (svmap[window_id_] != NULL) { svmap_mu->Unlock(); // So the event handling thread can quit. SendMsg("destroy()"); SVEvent* sve = AwaitEvent(SVET_DESTROY); delete sve; svmap_mu->Lock(); svmap[window_id_] = NULL; svmap_mu->Unlock(); // The event handler thread for this window *must* receive the // destroy event and set its pointer to this to NULL before we allow // the destructor to exit. while (!event_handler_ended_) Update(); } else { svmap_mu->Unlock(); } delete mutex_; delete semaphore_; delete points_; for (int i = 0; i < SVET_COUNT; i++) { delete event_table_[i]; } #endif // GRAPHICS_DISABLED } #ifndef GRAPHICS_DISABLED /// Send a message to the server, attaching the window id. void ScrollView::SendMsg(const char* format, ...) { if (!points_->empty) SendPolygon(); va_list args; char message[kMaxMsgSize]; va_start(args, format); // variable list vsnprintf(message, kMaxMsgSize, format, args); va_end(args); char form[kMaxMsgSize]; snprintf(form, kMaxMsgSize, "w%u:%s\n", window_id_, message); stream_->Send(form); } /// Send a message to the server without a /// window id. Used for global events like exit(). void ScrollView::SendRawMessage(const char* msg) { stream_->Send(msg); } /// Add an Event Listener to this ScrollView Window void ScrollView::AddEventHandler(SVEventHandler* listener) { event_handler_ = listener; } void ScrollView::Signal() { semaphore_->Signal(); } void ScrollView::SetEvent(SVEvent* svevent) { // Copy event SVEvent* any = svevent->copy(); SVEvent* specific = svevent->copy(); any->counter = specific->counter + 1; // Place both events into the queue. mutex_->Lock(); // Delete the old objects.. if (event_table_[specific->type] != NULL) { delete event_table_[specific->type]; } if (event_table_[SVET_ANY] != NULL) { delete event_table_[SVET_ANY]; } // ...and put the new ones in the table. event_table_[specific->type] = specific; event_table_[SVET_ANY] = any; mutex_->Unlock(); } /// Block until an event of the given type is received. /// Note: The calling function is responsible for deleting the returned /// SVEvent afterwards! SVEvent* ScrollView::AwaitEvent(SVEventType type) { // Initialize the waiting semaphore. SVSemaphore* sem = new SVSemaphore(); std::pair ea(this, type); waiting_for_events_mu->Lock(); waiting_for_events[ea] = std::pair (sem, (SVEvent*)0); waiting_for_events_mu->Unlock(); // Wait on it, but first flush. stream_->Flush(); sem->Wait(); // Process the event we got woken up for (its in waiting_for_events pair). waiting_for_events_mu->Lock(); SVEvent* ret = waiting_for_events[ea].second; waiting_for_events.erase(ea); delete sem; waiting_for_events_mu->Unlock(); return ret; } // Block until any event on any window is received. // No event is returned here! SVEvent* ScrollView::AwaitEventAnyWindow() { // Initialize the waiting semaphore. SVSemaphore* sem = new SVSemaphore(); std::pair ea((ScrollView*)0, SVET_ANY); waiting_for_events_mu->Lock(); waiting_for_events[ea] = std::pair (sem, (SVEvent*)0); waiting_for_events_mu->Unlock(); // Wait on it. stream_->Flush(); sem->Wait(); // Process the event we got woken up for (its in waiting_for_events pair). waiting_for_events_mu->Lock(); SVEvent* ret = waiting_for_events[ea].second; waiting_for_events.erase(ea); waiting_for_events_mu->Unlock(); return ret; } // Send the current buffered polygon (if any) and clear it. void ScrollView::SendPolygon() { if (!points_->empty) { points_->empty = true; // Allows us to use SendMsg. int length = points_->xcoords.size(); // length == 1 corresponds to 2 SetCursors in a row and only the // last setCursor has any effect. if (length == 2) { // An isolated line! SendMsg("drawLine(%d,%d,%d,%d)", points_->xcoords[0], points_->ycoords[0], points_->xcoords[1], points_->ycoords[1]); } else if (length > 2) { // A polyline. SendMsg("createPolyline(%d)", length); char coordpair[kMaxIntPairSize]; std::string decimal_coords; for (int i = 0; i < length; ++i) { snprintf(coordpair, kMaxIntPairSize, "%d,%d,", points_->xcoords[i], points_->ycoords[i]); decimal_coords += coordpair; } decimal_coords += '\n'; SendRawMessage(decimal_coords.c_str()); SendMsg("drawPolyline()"); } points_->xcoords.clear(); points_->ycoords.clear(); } } /******************************************************************************* * LUA "API" functions. *******************************************************************************/ // Sets the position from which to draw to (x,y). void ScrollView::SetCursor(int x, int y) { SendPolygon(); DrawTo(x, y); } // Draws from the current position to (x,y) and sets the new position to it. void ScrollView::DrawTo(int x, int y) { points_->xcoords.push_back(x); points_->ycoords.push_back(TranslateYCoordinate(y)); points_->empty = false; } // Draw a line using the current pen color. void ScrollView::Line(int x1, int y1, int x2, int y2) { if (!points_->xcoords.empty() && x1 == points_->xcoords.back() && TranslateYCoordinate(y1) == points_->ycoords.back()) { // We are already at x1, y1, so just draw to x2, y2. DrawTo(x2, y2); } else if (!points_->xcoords.empty() && x2 == points_->xcoords.back() && TranslateYCoordinate(y2) == points_->ycoords.back()) { // We are already at x2, y2, so just draw to x1, y1. DrawTo(x1, y1); } else { // This is a new line. SetCursor(x1, y1); DrawTo(x2, y2); } } // Set the visibility of the window. void ScrollView::SetVisible(bool visible) { if (visible) { SendMsg("setVisible(true)"); } else { SendMsg("setVisible(false)"); } } // Set the alwaysOnTop flag. void ScrollView::AlwaysOnTop(bool b) { if (b) { SendMsg("setAlwaysOnTop(true)"); } else { SendMsg("setAlwaysOnTop(false)"); } } // Adds a message entry to the message box. void ScrollView::AddMessage(const char* format, ...) { va_list args; char message[kMaxMsgSize]; char form[kMaxMsgSize]; va_start(args, format); // variable list vsnprintf(message, kMaxMsgSize, format, args); va_end(args); snprintf(form, kMaxMsgSize, "w%u:%s", window_id_, message); char* esc = AddEscapeChars(form); SendMsg("addMessage(\"%s\")", esc); delete[] esc; } // Set a messagebox. void ScrollView::AddMessageBox() { SendMsg("addMessageBox()"); } // Exit the client completely (and notify the server of it). void ScrollView::Exit() { SendRawMessage("svmain:exit()"); exit(0); } // Clear the canvas. void ScrollView::Clear() { SendMsg("clear()"); } // Set the stroke width. void ScrollView::Stroke(float width) { SendMsg("setStrokeWidth(%f)", width); } // Draw a rectangle using the current pen color. // The rectangle is filled with the current brush color. void ScrollView::Rectangle(int x1, int y1, int x2, int y2) { if (x1 == x2 && y1 == y2) return; // Scrollviewer locks up. SendMsg("drawRectangle(%d,%d,%d,%d)", x1, TranslateYCoordinate(y1), x2, TranslateYCoordinate(y2)); } // Draw an ellipse using the current pen color. // The ellipse is filled with the current brush color. void ScrollView::Ellipse(int x1, int y1, int width, int height) { SendMsg("drawEllipse(%d,%d,%u,%u)", x1, TranslateYCoordinate(y1), width, height); } // Set the pen color to the given RGB values. void ScrollView::Pen(int red, int green, int blue) { SendMsg("pen(%d,%d,%d)", red, green, blue); } // Set the pen color to the given RGB values. void ScrollView::Pen(int red, int green, int blue, int alpha) { SendMsg("pen(%d,%d,%d,%d)", red, green, blue, alpha); } // Set the brush color to the given RGB values. void ScrollView::Brush(int red, int green, int blue) { SendMsg("brush(%d,%d,%d)", red, green, blue); } // Set the brush color to the given RGB values. void ScrollView::Brush(int red, int green, int blue, int alpha) { SendMsg("brush(%d,%d,%d,%d)", red, green, blue, alpha); } // Set the attributes for future Text(..) calls. void ScrollView::TextAttributes(const char* font, int pixel_size, bool bold, bool italic, bool underlined) { const char* b; const char* i; const char* u; if (bold) { b = "true"; } else { b = "false"; } if (italic) { i = "true"; } else { i = "false"; } if (underlined) { u = "true"; } else { u = "false"; } SendMsg("textAttributes('%s',%u,%s,%s,%s)", font, pixel_size, b, i, u); } // Draw text at the given coordinates. void ScrollView::Text(int x, int y, const char* mystring) { SendMsg("drawText(%d,%d,'%s')", x, TranslateYCoordinate(y), mystring); } // Open and draw an image given a name at (x,y). void ScrollView::Image(const char* image, int x_pos, int y_pos) { SendMsg("openImage('%s')", image); SendMsg("drawImage('%s',%d,%d)", image, x_pos, TranslateYCoordinate(y_pos)); } // Add new checkboxmenuentry to menubar. void ScrollView::MenuItem(const char* parent, const char* name, int cmdEvent, bool flag) { if (parent == NULL) { parent = ""; } if (flag) { SendMsg("addMenuBarItem('%s','%s',%d,true)", parent, name, cmdEvent); } else { SendMsg("addMenuBarItem('%s','%s',%d,false)", parent, name, cmdEvent); } } // Add new menuentry to menubar. void ScrollView::MenuItem(const char* parent, const char* name, int cmdEvent) { if (parent == NULL) { parent = ""; } SendMsg("addMenuBarItem('%s','%s',%d)", parent, name, cmdEvent); } // Add new submenu to menubar. void ScrollView::MenuItem(const char* parent, const char* name) { if (parent == NULL) { parent = ""; } SendMsg("addMenuBarItem('%s','%s')", parent, name); } // Add new submenu to popupmenu. void ScrollView::PopupItem(const char* parent, const char* name) { if (parent == NULL) { parent = ""; } SendMsg("addPopupMenuItem('%s','%s')", parent, name); } // Add new submenuentry to popupmenu. void ScrollView::PopupItem(const char* parent, const char* name, int cmdEvent, const char* value, const char* desc) { if (parent == NULL) { parent = ""; } char* esc = AddEscapeChars(value); char* esc2 = AddEscapeChars(desc); SendMsg("addPopupMenuItem('%s','%s',%d,'%s','%s')", parent, name, cmdEvent, esc, esc2); delete[] esc; delete[] esc2; } // Send an update message for a single window. void ScrollView::UpdateWindow() { SendMsg("update()"); } // Note: this is an update to all windows void ScrollView::Update() { svmap_mu->Lock(); for (std::map::iterator iter = svmap.begin(); iter != svmap.end(); ++iter) { if (iter->second != NULL) iter->second->UpdateWindow(); } svmap_mu->Unlock(); } // Set the pen color, using an enum value (e.g. ScrollView::ORANGE) void ScrollView::Pen(Color color) { Pen(table_colors[color][0], table_colors[color][1], table_colors[color][2], table_colors[color][3]); } // Set the brush color, using an enum value (e.g. ScrollView::ORANGE) void ScrollView::Brush(Color color) { Brush(table_colors[color][0], table_colors[color][1], table_colors[color][2], table_colors[color][3]); } // Shows a modal Input Dialog which can return any kind of String char* ScrollView::ShowInputDialog(const char* msg) { SendMsg("showInputDialog(\"%s\")", msg); SVEvent* ev; // wait till an input event (all others are thrown away) ev = AwaitEvent(SVET_INPUT); char* p = new char[strlen(ev->parameter) + 1]; strncpy(p, ev->parameter, strlen(ev->parameter)); p[strlen(ev->parameter)] = '\0'; delete ev; return p; } // Shows a modal Yes/No Dialog which will return 'y' or 'n' int ScrollView::ShowYesNoDialog(const char* msg) { SendMsg("showYesNoDialog(\"%s\")", msg); SVEvent* ev; // Wait till an input event (all others are thrown away) ev = AwaitEvent(SVET_INPUT); int a = ev->parameter[0]; delete ev; return a; } // Zoom the window to the rectangle given upper left corner and // lower right corner. void ScrollView::ZoomToRectangle(int x1, int y1, int x2, int y2) { y1 = TranslateYCoordinate(y1); y2 = TranslateYCoordinate(y2); SendMsg("zoomRectangle(%d,%d,%d,%d)", MIN(x1, x2), MIN(y1, y2), MAX(x1, x2), MAX(y1, y2)); } // Send an image of type Pix. void ScrollView::Image(struct Pix* image, int x_pos, int y_pos) { l_uint8* data; size_t size; pixWriteMem(&data, &size, image, IFF_PNG); int base64_len = (size + 2) / 3 * 4; y_pos = TranslateYCoordinate(y_pos); SendMsg("readImage(%d,%d,%d)", x_pos, y_pos, base64_len); // Base64 encode the data. const char kBase64Table[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; char* base64 = new char[base64_len + 1]; memset(base64, '=', base64_len); base64[base64_len] = '\0'; int remainder = 0; int bits_left = 0; int code_len = 0; for (size_t i = 0; i < size; ++i) { int code = (data[i] >> (bits_left + 2)) | remainder; base64[code_len++] = kBase64Table[code & 63]; bits_left += 2; remainder = data[i] << (6 - bits_left); if (bits_left == 6) { base64[code_len++] = kBase64Table[remainder & 63]; bits_left = 0; remainder = 0; } } if (bits_left > 0) base64[code_len++] = kBase64Table[remainder & 63]; SendRawMessage(base64); delete [] base64; free(data); } // Escapes the ' character with a \, so it can be processed by LUA. // Note: The caller will have to make sure he deletes the newly allocated item. char* ScrollView::AddEscapeChars(const char* input) { const char* nextptr = strchr(input, '\''); const char* lastptr = input; char* message = new char[kMaxMsgSize]; int pos = 0; while (nextptr != NULL) { strncpy(message+pos, lastptr, nextptr-lastptr); pos += nextptr - lastptr; message[pos] = '\\'; pos += 1; lastptr = nextptr; nextptr = strchr(nextptr+1, '\''); } strncpy(message+pos, lastptr, strlen(lastptr)); message[pos+strlen(lastptr)] = '\0'; return message; } // Inverse the Y axis if the coordinates are actually inversed. int ScrollView::TranslateYCoordinate(int y) { if (!y_axis_is_reversed_) { return y; } else { return y_size_ - y; } } #endif // GRAPHICS_DISABLED