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
CreateInstance(IUIApplication ** out,HWND hwnd)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
InitializeRibbon(HWND hwnd)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
LoadRibbonState(IUIRibbon * ribbon)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
SaveRibbonState()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
GetRibbonHeight()171 LeashUIApplication::GetRibbonHeight()
172 {
173 return ribbonHeight;
174 }
175
176 ULONG
AddRef()177 LeashUIApplication::AddRef()
178 {
179 return InterlockedIncrement(&refcnt);
180 }
181
182 ULONG
Release()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
QueryInterface(REFIID iid,void ** ppv)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
OnViewChanged(UINT32 viewId,UI_VIEWTYPE typeID,IUnknown * view,UI_VIEWVERB verb,INT32 uReasonCode)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
OnCreateUICommand(UINT32 commandId,UI_COMMANDTYPE typeID,IUICommandHandler ** commandHandler)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
OnDestroyUICommand(UINT32 commandId,UI_COMMANDTYPE typeID,IUICommandHandler * commandHandler)287 LeashUIApplication::OnDestroyUICommand(UINT32 commandId, UI_COMMANDTYPE typeID,
288 IUICommandHandler *commandHandler)
289 {
290 return S_OK;
291 }
292