1 // -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- 2 // leash/LeashUIApplication.cpp - Implement IUIApplication for leash 3 // 4 // Copyright (C) 2014 by the Massachusetts Institute of Technology. 5 // All rights reserved. 6 // 7 // Redistribution and use in source and binary forms, with or without 8 // modification, are permitted provided that the following conditions 9 // are met: 10 // 11 // * Redistributions of source code must retain the above copyright 12 // notice, this list of conditions and the following disclaimer. 13 // 14 // * Redistributions in binary form must reproduce the above copyright 15 // notice, this list of conditions and the following disclaimer in 16 // the documentation and/or other materials provided with the 17 // distribution. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 24 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 // OF THE POSSIBILITY OF SUCH DAMAGE. 31 32 // Implementation of the LeashUIApplication class. In addition 33 // to the minimum requirements for the IUIApplication interface, 34 // it also saves and loads the ribbon state across application 35 // sessions, and initiates a redraw of the parent window when 36 // the ribbon size changes. 37 38 #include <UIRibbon.h> 39 #include <UIRibbonPropertyHelpers.h> 40 #include "kfwribbon.h" 41 #include "LeashUIApplication.h" 42 #include "LeashUICommandHandler.h" 43 44 HWND LeashUIApplication::mainwin; 45 46 // The input hwnd is the window to which to bind the ribbon, i.e., 47 // the Leash CMainFrame. 48 HRESULT 49 LeashUIApplication::CreateInstance(IUIApplication **out, HWND hwnd) 50 { 51 LeashUIApplication *app = NULL; 52 LeashUICommandHandler *handler; 53 HRESULT ret; 54 55 if (out == NULL) 56 return E_POINTER; 57 *out = NULL; 58 59 app = new LeashUIApplication(); 60 ret = LeashUICommandHandler::CreateInstance(&app->commandHandler, hwnd); 61 if (FAILED(ret)) 62 goto out; 63 ret = app->InitializeRibbon(hwnd); 64 if (FAILED(ret)) 65 goto out; 66 mainwin = hwnd; 67 // Only the Leash-specific handler type has the back-pointer. 68 handler = static_cast<LeashUICommandHandler *>(app->commandHandler); 69 handler->app = app; 70 *out = static_cast<IUIApplication *>(app); 71 app = NULL; 72 ret = S_OK; 73 74 out: 75 if (app != NULL) 76 app->Release(); 77 return ret; 78 } 79 80 // Create a ribbon framework and ribbon for the LeashUIApplication. 81 // CoInitializeEx() is required to be called before calling any COM 82 // functions. AfxOleInit(), called from CLeashApp::InitInstance(), 83 // makes that call, but it is only scoped to the calling thread, 84 // and the LeashUIApplication is created from CMainFrame, which is 85 // the frame for the MFC document template. It is unclear if the 86 // Leash main thread will be the same thread which runs the frame 87 // from the document template, so call CoInitializeEx() ourselves 88 // just in case. It is safe to call multiple times (it will return 89 // S_FALSE on subsequent calls). 90 HRESULT 91 LeashUIApplication::InitializeRibbon(HWND hwnd) 92 { 93 HRESULT ret; 94 95 if (hwnd == NULL) 96 return -1; 97 ret = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 98 if (FAILED(ret)) 99 return ret; 100 ret = CoCreateInstance(CLSID_UIRibbonFramework, NULL, 101 CLSCTX_INPROC_SERVER, 102 IID_PPV_ARGS(&ribbonFramework)); 103 if (FAILED(ret)) 104 return ret; 105 ret = ribbonFramework->Initialize(hwnd, this); 106 if (FAILED(ret)) 107 return ret; 108 ret = ribbonFramework->LoadUI(GetModuleHandle(NULL), 109 L"KFW_RIBBON_RIBBON"); 110 if (FAILED(ret)) 111 return ret; 112 return S_OK; 113 } 114 115 // Import ribbon state (minimization state and Quick Access Toolbar 116 // customizations) from a serialized stream stored in the registry. 117 // In particular, the serialized state does not include the state 118 // of checkboxes and other ribbon controls. 119 // 120 // This functionality is not very important, since we do not offer 121 // much in the way of QAT customization. Paired with SaveRibbonState(). 122 HRESULT 123 LeashUIApplication::LoadRibbonState(IUIRibbon *ribbon) 124 { 125 HRESULT ret; 126 IStream *s; 127 128 s = SHOpenRegStream2(HKEY_CURRENT_USER, "Software\\MIT\\Kerberos5", 129 "RibbonState", STGM_READ); 130 if (s == NULL) 131 return E_FAIL; 132 ret = ribbon->LoadSettingsFromStream(s); 133 s->Release(); 134 return ret; 135 } 136 137 // Serialize the ribbon state (minimization state and Quick Access Toolbar 138 // customizations) to the registry. Paired with LoadRibbonState(). 139 HRESULT 140 LeashUIApplication::SaveRibbonState() 141 { 142 HRESULT ret; 143 IStream *s = NULL; 144 IUIRibbon *ribbon = NULL; 145 146 // No ribbon means no state to save. 147 if (ribbonFramework == NULL) 148 return S_OK; 149 // ViewID of 0 is the ribbon itself. 150 ret = ribbonFramework->GetView(0, IID_PPV_ARGS(&ribbon)); 151 if (FAILED(ret)) 152 return ret; 153 154 s = SHOpenRegStream2(HKEY_CURRENT_USER, "Software\\MIT\\Kerberos5", 155 "RibbonState", STGM_WRITE); 156 if (s == NULL) { 157 ret = E_FAIL; 158 goto out; 159 } 160 ret = ribbon->SaveSettingsToStream(s); 161 162 out: 163 if (s != NULL) 164 s->Release(); 165 if (ribbon != NULL) 166 ribbon->Release(); 167 return ret; 168 } 169 170 UINT 171 LeashUIApplication::GetRibbonHeight() 172 { 173 return ribbonHeight; 174 } 175 176 ULONG 177 LeashUIApplication::AddRef() 178 { 179 return InterlockedIncrement(&refcnt); 180 } 181 182 ULONG 183 LeashUIApplication::Release() 184 { 185 LONG tmp; 186 187 tmp = InterlockedDecrement(&refcnt); 188 if (tmp == 0) { 189 if (commandHandler != NULL) 190 commandHandler->Release(); 191 if (ribbonFramework != NULL) 192 ribbonFramework->Release(); 193 delete this; 194 } 195 return tmp; 196 } 197 198 HRESULT 199 LeashUIApplication::QueryInterface(REFIID iid, void **ppv) 200 { 201 if (ppv == NULL) 202 return E_POINTER; 203 204 if (iid == __uuidof(IUnknown)) { 205 *ppv = static_cast<IUnknown*>(this); 206 } else if (iid == __uuidof(IUIApplication)) { 207 *ppv = static_cast<IUIApplication*>(this); 208 } else { 209 *ppv = NULL; 210 return E_NOINTERFACE; 211 } 212 213 AddRef(); 214 return S_OK; 215 } 216 217 // This is called by the ribbon framework on events which change the (ribbon) 218 // view, such as creation and resizing. (There may be other non-ribbon views 219 // in the future, but for now, the ribbon is the only one.) With the hybrid 220 // COM/MFC setup used by Leash, the destroy event is not always received, 221 // since the main thread is in the MFC half, and that thread gets the 222 // WM_DESTROY message from the system; the MFC code does not know that it 223 // needs to cleanly destroy the IUIFramework. 224 HRESULT 225 LeashUIApplication::OnViewChanged(UINT32 viewId, UI_VIEWTYPE typeID, 226 IUnknown *view, UI_VIEWVERB verb, 227 INT32 uReasonCode) 228 { 229 IUIRibbon *ribbon; 230 HRESULT ret; 231 232 // A viewId means "the ribbon". 233 if (viewId != 0 || typeID != UI_VIEWTYPE_RIBBON) 234 return E_NOTIMPL; 235 236 switch(verb) { 237 case UI_VIEWVERB_DESTROY: 238 return SaveRibbonState(); 239 case UI_VIEWVERB_CREATE: 240 ret = view->QueryInterface(IID_PPV_ARGS(&ribbon)); 241 if (FAILED(ret)) 242 return ret; 243 ret = LoadRibbonState(ribbon); 244 ribbon->Release(); 245 if (FAILED(ret)) 246 return ret; 247 // FALLTHROUGH 248 case UI_VIEWVERB_SIZE: 249 ret = view->QueryInterface(IID_PPV_ARGS(&ribbon)); 250 if (FAILED(ret)) 251 return ret; 252 ret = ribbon->GetHeight(&ribbonHeight); 253 ribbon->Release(); 254 if (FAILED(ret)) 255 return ret; 256 // Tell the main frame to recalculate its layout and redraw. 257 SendMessage(mainwin, WM_RIBBON_RESIZE, 0, NULL); 258 return S_OK; 259 case UI_VIEWVERB_ERROR: 260 // FALLTHROUGH 261 default: 262 return E_NOTIMPL; 263 } 264 } 265 266 // Provide a command handler to which the command with ID commandId will 267 // be bound. All of our commands get the same handler. 268 // 269 // The typeID argument is just an enum which classifies what type of 270 // command this is, grouping types of buttons together, collections, 271 // etc. Since we only have one command handler, it can safely be ignored. 272 HRESULT 273 LeashUIApplication::OnCreateUICommand(UINT32 commandId, UI_COMMANDTYPE typeID, 274 IUICommandHandler **commandHandler) 275 { 276 return this->commandHandler->QueryInterface(IID_PPV_ARGS(commandHandler)); 277 } 278 279 // It looks like this is called by the framework when the window with the 280 // ribbon is going away, to give the application a chance to free any 281 // application-specific resources (not from the framework) that were bound 282 // to a command in OnCreateUICommand. 283 // 284 // We do not have any such resources, so we do not need to implement this 285 // function other than by returning success. 286 HRESULT 287 LeashUIApplication::OnDestroyUICommand(UINT32 commandId, UI_COMMANDTYPE typeID, 288 IUICommandHandler *commandHandler) 289 { 290 return S_OK; 291 } 292