dialog.cpp

Go to the documentation of this file.
00001 /*
00002  * dialog.cpp
00003  *
00004  * Copyright (C) 2008-2009  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  *
00031  * Implementation of the simple dialog library.  See dialog.h
00032  */
00033 
00034 // includes --------------------------------------------------------------------
00035 #include "dialog.h"             // always include our own header first!
00036 
00037 #include "container.h"
00038 #include "request.h"
00039 
00040 #include "common/wave_ex.h"
00041 #include "datahash/datahash_text.h"
00042 #include "datahash/datahash_util.h"
00043 #include "perf/perf.h"
00044 
00045 
00046 namespace dialog {
00047 
00048 /// \ingroup dialog
00049 /*@{*/
00050 
00051 
00052 // Manager interface destructor
00053 Manager::~Manager(void) throw() { }
00054 
00055 // Host interface destructor
00056 Host::~Host(void) throw() { }
00057 
00058 
00059 struct dialog_record_t {
00060         rect_t                  bounds;         // 2D bounding rect
00061         Host *                  host;           // can be null!  callbacks
00062         smart_ptr<Element>      root;
00063 };
00064 
00065 
00066 typedef std::map<std::string, dialog_record_t > dialog_map_t;
00067 
00068 
00069 ////////////////////////////////////////////////////////////////////////////////
00070 //
00071 //      static helper methods
00072 //
00073 ////////////////////////////////////////////////////////////////////////////////
00074 
00075 smart_ptr<Datahash>
00076 ContainerRequest::getDatahash
00077 (
00078 void
00079 )
00080 {
00081         this->close();
00082         std::istringstream iss(this->get());
00083 
00084         return readHashFromStream("dialog", iss);
00085 }
00086 
00087 
00088 
00089 ////////////////////////////////////////////////////////////////////////////////
00090 //
00091 //      Mgr -- implementation for the dialog::Manager interface
00092 //
00093 ////////////////////////////////////////////////////////////////////////////////
00094 
00095 class Mgr : public Manager {
00096 public:
00097         ~Mgr(void) throw() { }
00098 
00099         // public class methods ------------------------------------------------
00100         void initialize(IN smart_ptr<crypto::DESKey>& desKey,
00101                                 IN smart_ptr<Drawer>& drawer);
00102 
00103         // dialog::Manager class interface methods -----------------------------
00104         smart_ptr<Drawer> getDrawer(void) { return m_drawer; }
00105         void registerFactory(IN const char * type,
00106                                 IN smart_ptr<Factory>& factory);
00107         Factory * getFactory(IN const char * type);
00108         void display(IN int screen_w, IN int screen_h);
00109         void cursor(IN int x, IN int y);
00110         void button(IN int button, IN int state, IN int x, IN int y);
00111         void keyboard(IN int key, IN int mods);
00112         bool createDialog(IN const char * id,
00113                                 IN int x, IN int y,
00114                                 IN const Datahash * form,
00115                                 IN Host * host);
00116         bool doesDialogExist(IN const char * id);
00117         void destroyDialog(IN const char * id);
00118 
00119 private:
00120         // private typedefs ----------------------------------------------------
00121         typedef std::map<std::string, smart_ptr<Factory> > factory_map_t;
00122 
00123         // private helper methods ----------------------------------------------
00124         dialog_record_t * getDialog(IN const char * id);
00125         void submit(IN const char * id, IN const char * result);
00126 
00127         // private member data -------------------------------------------------
00128         factory_map_t                   m_factories;
00129         dialog_map_t                    m_dialogs;
00130         rect_t                          m_viewport;
00131         std::string                     m_cursorTag;
00132         bool                            m_cursorDown;
00133         std::string                     m_dragId;
00134         point_t                         m_dragStart;
00135         Element *                       m_focus;        // keyboard focus
00136         std::string                     m_focusId;
00137         smart_ptr<crypto::DESKey>       m_desKey;
00138         smart_ptr<Drawer>               m_drawer;
00139 };
00140 
00141 
00142 
00143 void
00144 Mgr::initialize
00145 (
00146 IN smart_ptr<crypto::DESKey>& desKey,
00147 IN smart_ptr<Drawer>& drawer
00148 )
00149 {
00150         // ASSERT(desKey) -- can be null!
00151         m_desKey = desKey;
00152 
00153         ASSERT(drawer, "null");
00154         ASSERT(!m_drawer, "already have a drawer?");
00155         m_drawer = drawer;
00156 
00157         // nothing to do yet...
00158         m_cursorDown = false;
00159         m_cursorTag = "";
00160         m_dragId = "";
00161         m_focus = NULL;
00162 
00163         // register default types
00164         registerDefaultFactories(this);
00165 }
00166 
00167 
00168 
00169 ////////////////////////////////////////////////////////////////////////////////
00170 //
00171 //      Mgr -- dialog::Manager class interface methods
00172 //
00173 ////////////////////////////////////////////////////////////////////////////////
00174 
00175 void
00176 Mgr::registerFactory
00177 (
00178 IN const char * type,
00179 IN smart_ptr<Factory>& factory
00180 )
00181 {
00182         ASSERT(type, "null");
00183         ASSERT(factory, "null");
00184 
00185         // don't worry about collisions--last one in wins
00186         m_factories[type] = factory;
00187 }
00188 
00189 
00190 
00191 void
00192 Mgr::display
00193 (
00194 IN int screen_w,
00195 IN int screen_h
00196 )
00197 {
00198         // loop through all dialogs and ask them to draw
00199         for (dialog_map_t::iterator i = m_dialogs.begin(); i != m_dialogs.end();
00200              ++i) {
00201                 dialog_record_t& dr = i->second;
00202                 ASSERT(dr.root, "null");
00203 
00204                 // get this dialog's width and height
00205                 int width = dr.bounds.right - dr.bounds.left;
00206                 int height = dr.bounds.bottom - dr.bounds.top;
00207 
00208                 // the x,y position we are talking about is the dialog's top
00209                 //   left
00210 
00211                 // minimum X: lesser of 0 or screen width - dialog width
00212                 int minX = screen_w - width;
00213                 if (minX > 0) {
00214                         minX = 0;
00215                 }
00216                 int minY = screen_h - height;
00217                 if (minY > 0) {
00218                         minY = 0;
00219                 }
00220 
00221                 // clip!
00222                 int dx, dy;
00223                 dx = dy = 0;
00224                 if (dr.bounds.left < minX) {
00225                         dx = minX - dr.bounds.left;
00226                 }
00227                 if (dr.bounds.top < minY) {
00228                         dy = minY - dr.bounds.top;
00229                 }
00230                 dr.bounds.shift(dx, dy);
00231 
00232                 // maximum X: max of 0 or screen width - dialog width
00233                 int maxX = screen_w - width;
00234                 if (maxX < 0)
00235                         maxX = 0;
00236                 int maxY = screen_h - height;
00237                 if (maxY < 0)
00238                         maxY = 0;
00239                 if (dr.bounds.left > maxX) {
00240                         dx = maxX - dr.bounds.left;
00241                 }
00242                 if (dr.bounds.top > maxY) {
00243                         dy = maxY - dr.bounds.top;
00244                 }
00245                 dr.bounds.shift(dx, dy);
00246 
00247                 // give the entire dialog a common background
00248                 m_drawer->drawRectWithBorder(eElement_Dialog, dr.bounds);
00249 
00250                 // now ask elements to draw recursively
00251                 dr.root->draw(dr.bounds.getTopLeft());
00252         }
00253 }
00254 
00255 
00256 
00257 
00258 void
00259 Mgr::button
00260 (
00261 IN int button,
00262 IN int state,
00263 IN int x,
00264 IN int y
00265 )
00266 {
00267         // is this a button we care about?
00268         //DPRINTF("button = %d", button);
00269         if (button)
00270                 return;         // only care about first button
00271 
00272         point_t pos(x, y);
00273 
00274         const char * result = NULL;
00275         const char * id = NULL;
00276         for (dialog_map_t::iterator i = m_dialogs.begin(); i != m_dialogs.end();
00277              ++i) {
00278                 dialog_record_t& dr = i->second;
00279                 ASSERT(dr.root, "null");
00280 
00281                 if (!dr.bounds.contains(pos))
00282                         continue;               // skip this one
00283 
00284                 // convert to element coordinates
00285                 result = dr.root->button(button, state,
00286                     pos - dr.bounds.getTopLeft());
00287                 id = i->first.c_str();          // in bounding box
00288 
00289                 // press down?  check focus
00290                 if (!state) {
00291                         Element * focus =
00292                             dr.root->getFocus(pos - dr.bounds.getTopLeft());
00293                         DPRINTF("Focus: %p", m_focus);
00294                         if (focus) {
00295                                 if (m_focus) {
00296                                         m_focus->notifyFocus(false);
00297                                 }
00298                                 m_focus = focus;
00299                                 m_focus->notifyFocus(true);
00300                                 m_focusId = id;
00301                         }
00302                 }
00303         }
00304 
00305         // was this a release?
00306         if (state) {
00307                 // yes, a release
00308                 const char * oldTag = m_cursorTag.c_str();
00309                 if (result && !strcmp(result, oldTag)) {
00310                         DPRINTF("Cursor down + up on same element!");
00311                         this->submit(id, result);
00312                 }
00313 
00314                 // clear out cursor state
00315                 m_cursorTag = "";
00316                 m_cursorDown = false;
00317                 m_dragId = "";
00318         } else {
00319                 // this was a cursor down
00320                 m_cursorTag = result ? result : "";
00321                 m_cursorDown = true;
00322 
00323                 // nothing hit?  then it is a drag
00324                 if (!result && id) {
00325                         m_dragId = id;
00326                         m_dragStart.set(x, y);
00327                 }
00328         }
00329 
00330 /*
00331         if (result) {
00332                 DPRINTF("Got %s", result);
00333         }
00334  */
00335 }
00336 
00337 
00338 
00339 void
00340 Mgr::cursor
00341 (
00342 IN int x,
00343 IN int y
00344 )
00345 {
00346         // only care if we are dragging
00347         if (m_cursorDown && !m_dragId.empty()) {
00348                 // dragging!
00349                 point_t delta = point_t(x, y) - m_dragStart;
00350                 m_dragStart.set(x, y);
00351 
00352                 // look up record
00353                 dialog_record_t * pdr = this->getDialog(m_dragId.c_str());
00354                 if (!pdr) {
00355                         // gone?
00356                         m_dragId = "";
00357                 } else {
00358                         pdr->bounds.shift(delta.x, delta.y);
00359 
00360                 }
00361         }
00362 }
00363 
00364 
00365 
00366 void
00367 Mgr::keyboard
00368 (
00369 IN int key,
00370 IN int mods
00371 )
00372 {
00373         //DPRINTF("Got key=%d, mods=0x%08x", key, mods);
00374         if (m_focus) {
00375                 DPRINTF("  Someone has focus!");
00376                 const char * result = m_focus->keyboard(key, mods);
00377                 if (result) {
00378                         DPRINTF("Submit after key event!  '%s'", result);
00379                         this->submit(m_focusId.c_str(), result);
00380                 }
00381         }
00382 }
00383 
00384 
00385 
00386 ////////////////////////////////////////////////////////////////////////////////
00387 //
00388 //      Mgr -- private helper methods
00389 //
00390 ////////////////////////////////////////////////////////////////////////////////
00391 
00392 dialog_record_t *
00393 Mgr::getDialog
00394 (
00395 IN const char * id
00396 )
00397 {
00398         ASSERT(id, "null");
00399 
00400         dialog_map_t::iterator i = m_dialogs.find(id);
00401         if (m_dialogs.end() == i) {
00402                 return NULL;            // not found
00403         }
00404 
00405         return &i->second;
00406 }
00407 
00408 
00409 
00410 void
00411 Mgr::submit
00412 (
00413 IN const char * id,
00414 IN const char * result
00415 )
00416 {
00417         ASSERT(id, "null");
00418         ASSERT(result, "null");
00419 
00420         dialog_record_t * pdr = this->getDialog(id);
00421         if (!pdr || !pdr->host)
00422                 return;         // stale data???
00423         ASSERT(pdr->root, "null root");
00424 
00425         // need to notify host
00426         smart_ptr<Datahash> data = Datahash::create();
00427         ASSERT(data, "failed to create empty hash");
00428         data->insert("submit", result);
00429         pdr->root->addData(m_desKey, data);
00430         pdr->host->notifySubmit(id, data);
00431 
00432         // clear focus etc just in case
00433         m_focus = NULL;
00434         m_focusId = "";
00435 }
00436 
00437 
00438 
00439 /// creates a dialog at the specified x,y position (actual screen position
00440 /// depends on where the client has positioned the DialogManager).
00441 ///
00442 /// Returns false if the dialog cannot be constructed.  Typically this only
00443 /// happens if the ID is itself invalid.
00444 bool
00445 Mgr::createDialog
00446 (
00447 IN const char * id,
00448 IN int x,
00449 IN int y,
00450 IN const Datahash * data,
00451 IN Host * host
00452 )
00453 {
00454         perf::Timer timer("createDialog");
00455         ASSERT(id, "null");
00456         ASSERT(data, "null");
00457         // ASSERT(host) -- we don't care!
00458 
00459         //DPRINTF("In createDialog()...");
00460 
00461         // valid id?
00462         if (!*id) {
00463                 DPRINTF("Don't allow empty IDs...");
00464                 return false;
00465         }
00466 
00467         // does this dialog already exist?
00468         // we don't care!  A duplicate request will take precedence
00469         // actually, we kind of care--if this is a duplicate, we'll use the
00470         //      existing xy coordinates
00471         dialog_record_t * pdr = this->getDialog(id);
00472         if (pdr) {
00473                 // override what was passed in -- use existing xy instead
00474                 x = pdr->bounds.left;
00475                 y = pdr->bounds.top;
00476         }
00477 
00478         dialog_record_t dr;
00479         dr.host = host;
00480         dr.root = constructElementTree(this, data);
00481         ASSERT(dr.root, "failed to construct dialog element tree");
00482 
00483         // determine bounding rect
00484         int w = dr.root->getWidth();
00485         int h = dr.root->getHeight();
00486         ASSERT(w > 0 && h > 0, "Bad dialog width or height: %d x %d", w, h);
00487         dr.bounds.set(x, y, x + w, y + h);
00488 
00489         // save record
00490         m_dialogs[id] = dr;
00491 
00492         return true;
00493 }
00494 
00495 
00496 
00497 bool
00498 Mgr::doesDialogExist
00499 (
00500 IN const char * id
00501 )
00502 {
00503         ASSERT(id, "null");
00504 
00505         return this->getDialog(id) ? true : false;
00506 }
00507 
00508 
00509 
00510 void
00511 Mgr::destroyDialog
00512 (
00513 IN const char * id
00514 )
00515 {
00516         ASSERT(id, "null");
00517 
00518         dialog_map_t::iterator i = m_dialogs.find(id);
00519         if (m_dialogs.end() == i) {
00520                 DPRINTF("No dialog to destroy!  Id='%s'", id);
00521                 return;
00522         }
00523 
00524         // clear out focus, just in case
00525         m_focus = NULL;
00526         m_focusId = "";
00527 
00528         m_dialogs.erase(i);
00529 }
00530 
00531 
00532 
00533 Factory *
00534 Mgr::getFactory
00535 (
00536 IN const char * type
00537 )
00538 {
00539         ASSERT(type, "null");
00540 
00541         factory_map_t::iterator i = m_factories.find(type);
00542         if (m_factories.end() == i) {
00543                 DPRINTF("No factory found for dialog element type: '%s'", type);
00544                 return NULL;
00545         }
00546         ASSERT(i->second, "null factory in map");
00547         return i->second;
00548 }
00549 
00550 
00551 
00552 ////////////////////////////////////////////////////////////////////////////////
00553 //
00554 //      public API
00555 //
00556 ////////////////////////////////////////////////////////////////////////////////
00557 
00558 smart_ptr<Manager>
00559 Manager::create
00560 (
00561 IN smart_ptr<crypto::DESKey>& desKey,
00562 IN smart_ptr<Drawer>& drawer
00563 )
00564 {
00565         // ASSERT(desKey) -- can be null
00566         ASSERT(drawer, "null");
00567 
00568         smart_ptr<Mgr> local = new Mgr;
00569         ASSERT(local, "out of memory");
00570 
00571         local->initialize(desKey, drawer);
00572 
00573         return local;
00574 }
00575 
00576 
00577 
00578 };      // dialog namespace
00579