even more funny icon arrangements
[reactos.git] / reactos / subsys / system / explorer / desktop / desktop.cpp
1 /*
2 * Copyright 2003 Martin Fuchs
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18
19
20 //
21 // Explorer clone
22 //
23 // desktop.cpp
24 //
25 // Martin Fuchs, 09.08.2003
26 //
27
28
29 #include "../utility/utility.h"
30 #include "../utility/shellclasses.h"
31 #include "../utility/shellbrowserimpl.h"
32 #include "../utility/dragdropimpl.h"
33 #include "../utility/window.h"
34
35 #include "../globals.h"
36 #include "../externals.h"
37 #include "../explorer_intres.h"
38
39 #include "desktop.h"
40 #include "../taskbar/desktopbar.h"
41 #include "../shell/mainframe.h" // for MainFrame::Create()
42
43
44 static BOOL (WINAPI*SetShellWindow)(HWND);
45 static BOOL (WINAPI*SetShellWindowEx)(HWND, HWND);
46
47
48 BOOL IsAnyDesktopRunning()
49 {
50 HINSTANCE hUser32 = GetModuleHandle(TEXT("user32"));
51
52 SetShellWindow = (BOOL(WINAPI*)(HWND)) GetProcAddress(hUser32, "SetShellWindow");
53 SetShellWindowEx = (BOOL(WINAPI*)(HWND,HWND)) GetProcAddress(hUser32, "SetShellWindowEx");
54
55 return GetShellWindow() != 0;
56 }
57
58
59 static void draw_desktop_background(HWND hwnd, HDC hdc)
60 {
61 ClientRect rect(hwnd);
62
63 PaintDesktop(hdc);
64 /*
65 HBRUSH bkgndBrush = CreateSolidBrush(RGB(0,32,160)); // dark blue
66 FillRect(hdc, &rect, bkgndBrush);
67 DeleteBrush(bkgndBrush);
68 */
69
70 rect.left = rect.right - 280;
71 rect.top = rect.bottom - 56 - DESKTOPBARBAR_HEIGHT;
72 rect.right = rect.left + 250;
73 rect.bottom = rect.top + 40;
74
75 #include "../buildno.h"
76 static const LPCTSTR BkgndText = TEXT("ReactOS ")TEXT(KERNEL_VERSION_STR)TEXT(" Explorer\nby Martin Fuchs");
77
78 BkMode bkMode(hdc, TRANSPARENT);
79
80 TextColor textColor(hdc, RGB(128,128,192));
81 DrawText(hdc, BkgndText, -1, &rect, DT_RIGHT);
82
83 SetTextColor(hdc, RGB(255,255,255));
84 --rect.right;
85 ++rect.top;
86 DrawText(hdc, BkgndText, -1, &rect, DT_RIGHT);
87 }
88
89
90 LRESULT BackgroundWindow::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
91 {
92 switch(nmsg) {
93 case WM_ERASEBKGND:
94 PaintDesktop((HDC)wparam);
95 return TRUE;
96
97 case WM_MBUTTONDBLCLK:
98 explorer_show_frame(_hwnd, SW_SHOWNORMAL);
99 break;
100
101 default:
102 return super::WndProc(nmsg, wparam, lparam);
103 }
104
105 return 0;
106 }
107
108
109 DesktopWindow::DesktopWindow(HWND hwnd)
110 : super(hwnd)
111 {
112 _pShellView = NULL;
113 }
114
115 DesktopWindow::~DesktopWindow()
116 {
117 if (_pShellView)
118 _pShellView->Release();
119 }
120
121
122 HWND DesktopWindow::Create()
123 {
124 IconWindowClass wcDesktop(TEXT("Progman"), IDI_REACTOS, CS_DBLCLKS);
125 wcDesktop.hbrBackground = (HBRUSH)(COLOR_BACKGROUND+1);
126
127 int width = GetSystemMetrics(SM_CXSCREEN);
128 int height = GetSystemMetrics(SM_CYSCREEN);
129
130 HWND hwndDesktop = Window::Create(WINDOW_CREATOR(DesktopWindow),
131 WS_EX_TOOLWINDOW, wcDesktop, TEXT("Program Manager"), WS_POPUP|WS_VISIBLE|WS_CLIPCHILDREN,
132 0, 0, width, height, 0);
133
134 // work around to display desktop bar in Wine
135 ShowWindow(GET_WINDOW(DesktopWindow, hwndDesktop)->_desktopBar, SW_SHOW);
136
137 // work around for Windows NT, Win 98, ...
138 // Without this the desktop has mysteriously only a size of 800x600 pixels.
139 MoveWindow(hwndDesktop, 0, 0, width, height, TRUE);
140
141 return hwndDesktop;
142 }
143
144
145 LRESULT DesktopWindow::Init(LPCREATESTRUCT pcs)
146 {
147 if (super::Init(pcs))
148 return 1;
149
150 HRESULT hr = Desktop()->CreateViewObject(_hwnd, IID_IShellView, (void**)&_pShellView);
151 /* also possible:
152 SFV_CREATE sfv_create;
153
154 sfv_create.cbSize = sizeof(SFV_CREATE);
155 sfv_create.pshf = Desktop();
156 sfv_create.psvOuter = NULL;
157 sfv_create.psfvcb = NULL;
158
159 HRESULT hr = SHCreateShellFolderView(&sfv_create, &_pShellView);
160 */
161 HWND hWndView = 0;
162
163 if (SUCCEEDED(hr)) {
164 FOLDERSETTINGS fs;
165
166 fs.ViewMode = FVM_ICON;
167 fs.fFlags = FWF_DESKTOP|FWF_NOCLIENTEDGE|FWF_NOSCROLL|FWF_BESTFITWINDOW|FWF_SNAPTOGRID; //|FWF_AUTOARRANGE;
168
169 ClientRect rect(_hwnd);
170
171 hr = _pShellView->CreateViewWindow(NULL, &fs, this, &rect, &hWndView);
172
173 ///@todo use IShellBrowser::GetViewStateStream() to restore previous view state -> see SHOpenRegStream()
174
175 if (SUCCEEDED(hr)) {
176 g_Globals._hwndShellView = hWndView;
177
178 // subclass shellview window
179 new DesktopShellView(hWndView, _pShellView);
180
181 _pShellView->UIActivate(SVUIA_ACTIVATE_FOCUS);
182
183 /*
184 IShellView2* pShellView2;
185
186 hr = _pShellView->QueryInterface(IID_IShellView2, (void**)&pShellView2);
187
188 SV2CVW2_PARAMS params;
189 params.cbSize = sizeof(SV2CVW2_PARAMS);
190 params.psvPrev = _pShellView;
191 params.pfs = &fs;
192 params.psbOwner = this;
193 params.prcView = ▭
194 params.pvid = params.pvid;//@@
195
196 hr = pShellView2->CreateViewWindow2(&params);
197 params.pvid;
198 */
199
200 /*
201 IFolderView* pFolderView;
202
203 hr = _pShellView->QueryInterface(IID_IFolderView, (void**)&pFolderView);
204
205 if (SUCCEEDED(hr)) {
206 hr = pFolderView->GetAutoArrange();
207 hr = pFolderView->SetCurrentViewMode(FVM_DETAILS);
208 }
209 */
210 }
211 }
212
213 if (hWndView && SetShellWindowEx)
214 SetShellWindowEx(_hwnd, hWndView);
215 else if (SetShellWindow)
216 SetShellWindow(_hwnd);
217
218 // create the explorer bar
219 _desktopBar = DesktopBar::Create();
220 g_Globals._hwndDesktopBar = _desktopBar;
221
222 return 0;
223 }
224
225
226 LRESULT DesktopWindow::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
227 {
228 switch(nmsg) {
229 case WM_PAINT:
230 draw_desktop_background(_hwnd, PaintCanvas(_hwnd));
231 break;
232
233 case WM_LBUTTONDBLCLK:
234 case WM_RBUTTONDBLCLK:
235 case WM_MBUTTONDBLCLK:
236 explorer_show_frame(_hwnd, SW_SHOWNORMAL);
237 break;
238
239 case WM_GETISHELLBROWSER:
240 return (LRESULT)static_cast<IShellBrowser*>(this);
241
242 case WM_DESTROY:
243
244 ///@todo use IShellBrowser::GetViewStateStream() and _pShellView->SaveViewState() to store view state
245
246 if (SetShellWindow)
247 SetShellWindow(0);
248 break;
249
250 case WM_CLOSE:
251 ShowExitWindowsDialog(_hwnd);
252 break;
253
254 case WM_SYSCOMMAND:
255 if (wparam == SC_TASKLIST) {
256 if (_desktopBar)
257 SendMessage(_desktopBar, nmsg, wparam, lparam);
258 }
259 goto def;
260
261 default: def:
262 return super::WndProc(nmsg, wparam, lparam);
263 }
264
265 return 0;
266 }
267
268
269 HRESULT DesktopWindow::OnDefaultCommand(LPIDA pida)
270 {
271 if (MainFrame::OpenShellFolders(pida, 0))
272 return S_OK;
273
274 return E_NOTIMPL;
275 }
276
277
278 DesktopShellView::DesktopShellView(HWND hwnd, IShellView* pShellView)
279 : super(hwnd),
280 _pShellView(pShellView)
281 {
282 _hwndListView = ::GetNextWindow(hwnd, GW_CHILD);
283
284 SetWindowStyle(_hwndListView, GetWindowStyle(_hwndListView)&~LVS_ALIGNMASK);//|LVS_ALIGNTOP|LVS_AUTOARRANGE);
285
286 // work around for Windows NT, Win 98, ...
287 // Without this the desktop has mysteriously only a size of 800x600 pixels.
288 ClientRect rect(hwnd);
289 MoveWindow(_hwndListView, 0, 0, rect.right, rect.bottom, TRUE);
290
291 // subclass background window
292 new BackgroundWindow(_hwndListView);
293
294 _icon_algo = 0;
295
296 PositionIcons();
297 InitDragDrop();
298 }
299
300 bool DesktopShellView::InitDragDrop()
301 {
302 CONTEXT("DesktopShellView::InitDragDrop()");
303
304 _pDropTarget = new DesktopDropTarget(_hwnd);
305
306 if (!_pDropTarget)
307 return false;
308
309 _pDropTarget->AddRef();
310
311 if (FAILED(RegisterDragDrop(_hwnd, _pDropTarget))) {
312 _pDropTarget->Release();
313 _pDropTarget = NULL;
314 return false;
315 }
316 else
317 _pDropTarget->Release();
318
319 FORMATETC ftetc;
320
321 ftetc.dwAspect = DVASPECT_CONTENT;
322 ftetc.lindex = -1;
323 ftetc.tymed = TYMED_HGLOBAL;
324 ftetc.cfFormat = CF_HDROP;
325
326 _pDropTarget->AddSuportedFormat(ftetc);
327
328 return true;
329 }
330
331 LRESULT DesktopShellView::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
332 {
333 switch(nmsg) {
334 case WM_CONTEXTMENU:
335 if (!DoContextMenu(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)))
336 DoDesktopContextMenu(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
337 break;
338
339 case PM_SET_ICON_ALGORITHM:
340 _icon_algo = wparam;
341 PositionIcons();
342 break;
343
344 case PM_GET_ICON_ALGORITHM:
345 return _icon_algo;
346
347 default:
348 return super::WndProc(nmsg, wparam, lparam);
349 }
350
351 return 0;
352 }
353
354 int DesktopShellView::Command(int id, int code)
355 {
356 return super::Command(id, code);
357 }
358
359 int DesktopShellView::Notify(int id, NMHDR* pnmh)
360 {
361 return super::Notify(id, pnmh);
362 }
363
364 bool DesktopShellView::DoContextMenu(int x, int y)
365 {
366 IDataObject* selection;
367
368 HRESULT hr = _pShellView->GetItemObject(SVGIO_SELECTION, IID_IDataObject, (void**)&selection);
369 if (FAILED(hr))
370 return false;
371
372 PIDList pidList;
373
374 hr = pidList.GetData(selection);
375 if (FAILED(hr)) {
376 selection->Release();
377 //CHECKERROR(hr);
378 return false;
379 }
380
381 LPIDA pida = pidList;
382 if (!pida->cidl) {
383 selection->Release();
384 return false;
385 }
386
387 LPCITEMIDLIST parent_pidl = (LPCITEMIDLIST) ((LPBYTE)pida+pida->aoffset[0]);
388
389 LPCITEMIDLIST* apidl = (LPCITEMIDLIST*) alloca(pida->cidl*sizeof(LPCITEMIDLIST));
390
391 for(int i=pida->cidl; i>0; --i)
392 apidl[i-1] = (LPCITEMIDLIST) ((LPBYTE)pida+pida->aoffset[i]);
393
394 hr = ShellFolderContextMenu(ShellFolder(parent_pidl), _hwnd, pida->cidl, apidl, x, y);
395
396 selection->Release();
397
398 CHECKERROR(hr);
399
400 return true;
401 }
402
403 HRESULT DesktopShellView::DoDesktopContextMenu(int x, int y)
404 {
405 IContextMenu* pcm;
406
407 HRESULT hr = DesktopFolder()->GetUIObjectOf(_hwnd, 0, NULL, IID_IContextMenu, NULL, (LPVOID*)&pcm);
408
409 if (SUCCEEDED(hr)) {
410 HMENU hmenu = CreatePopupMenu();
411
412 if (hmenu) {
413 hr = pcm->QueryContextMenu(hmenu, 0, FCIDM_SHVIEWFIRST, FCIDM_SHVIEWLAST-1, CMF_NORMAL|CMF_EXPLORE);
414
415 if (SUCCEEDED(hr)) {
416 AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
417 AppendMenu(hmenu, 0, FCIDM_SHVIEWLAST-1, ResString(IDS_ABOUT_EXPLORER));
418
419 UINT idCmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN|TPM_RETURNCMD|TPM_RIGHTBUTTON, x, y, 0, _hwnd, NULL);
420
421 if (idCmd == FCIDM_SHVIEWLAST-1) {
422 explorer_about(_hwnd);
423 } else if (idCmd) {
424 CMINVOKECOMMANDINFO cmi;
425
426 cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
427 cmi.fMask = 0;
428 cmi.hwnd = _hwnd;
429 cmi.lpVerb = (LPCSTR)(INT_PTR)(idCmd - FCIDM_SHVIEWFIRST);
430 cmi.lpParameters = NULL;
431 cmi.lpDirectory = NULL;
432 cmi.nShow = SW_SHOWNORMAL;
433 cmi.dwHotKey = 0;
434 cmi.hIcon = 0;
435
436 hr = pcm->InvokeCommand(&cmi);
437 }
438 }
439 }
440
441 pcm->Release();
442 }
443
444 return hr;
445 }
446
447
448 #define ARRANGE_BORDER_DOWN 8
449 #define ARRANGE_BORDER_HV 9
450 #define ARRANGE_ROUNDABOUT 10
451
452 static const POINTS s_align_start[] = {
453 {0, 0}, // left/top
454 {0, 0},
455 {1, 0}, // right/top
456 {1, 0},
457 {0, 1}, // left/bottom
458 {0, 1},
459 {1, 1}, // right/bottom
460 {1, 1},
461
462 {0, 0}, // left/top
463 {0, 0},
464 {0, 0}
465 };
466
467 static const POINTS s_align_dir1[] = {
468 { 0, +1}, // down
469 {+1, 0}, // right
470 {-1, 0}, // left
471 { 0, +1}, // down
472 { 0, -1}, // up
473 {+1, 0}, // right
474 {-1, 0}, // left
475 { 0, -1}, // up
476
477 { 0, +1}, // down
478 {+1, 0}, // right
479 {+1, 0} // right
480 };
481
482 static const POINTS s_align_dir2[] = {
483 {+1, 0}, // right
484 { 0, +1}, // down
485 { 0, +1}, // down
486 {-1, 0}, // left
487 {+1, 0}, // right
488 { 0, -1}, // up
489 { 0, -1}, // up
490 {-1, 0}, // left
491
492 {+1, 0}, // right
493 { 0, +1}, // down
494 { 0, +1} // down
495 };
496
497 typedef pair<int,int> IconPos;
498 typedef map<IconPos, int> IconMap;
499
500 void DesktopShellView::PositionIcons(int dir)
501 {
502 DWORD spacing = ListView_GetItemSpacing(_hwndListView, FALSE);
503
504 RECT work_area;
505 SystemParametersInfo(SPI_GETWORKAREA, 0, &work_area, 0);
506
507 const POINTS& dir1 = s_align_dir1[_icon_algo];
508 const POINTS& dir2 = s_align_dir2[_icon_algo];
509 const POINTS& start_pos = s_align_start[_icon_algo];
510
511 int dir_x1 = dir1.x;
512 int dir_y1 = dir1.y;
513 int dir_x2 = dir2.x;
514 int dir_y2 = dir2.y;
515
516 int cx = LOWORD(spacing);
517 int cy = HIWORD(spacing);
518
519 int dx1 = dir_x1 * cx;
520 int dy1 = dir_y1 * cy;
521 int dx2 = dir_x2 * cx;
522 int dy2 = dir_y2 * cy;
523
524 int start_x = (start_pos.x * work_area.right)/cx*cx + (cx-32)/2;
525 int start_y = (start_pos.y * work_area.bottom)/cy*cy + 4/*(cy-32)/2*/;
526
527 if (start_x >= work_area.right)
528 start_x -= cx;
529
530 if (start_y >= work_area.bottom)
531 start_y -= cy;
532
533 int x = start_x;
534 int y = start_y;
535
536 int all = ListView_GetItemCount(_hwndListView);
537 int i1, i2;
538
539 if (dir > 0) {
540 i1 = 0;
541 i2 = all;
542 } else {
543 i1 = all-1;
544 i2 = -1;
545 }
546
547 IconMap pos_idx;
548 int cnt = 0;
549
550 for(int idx=i1; idx!=i2; idx+=dir) {
551 pos_idx[IconPos(y, x)] = idx;
552
553 if (_icon_algo == ARRANGE_BORDER_DOWN) {
554 if (++cnt & 1)
555 x = work_area.right - x;
556 else {
557 y += dy1;
558
559 if (y >= work_area.bottom) {
560 y = start_y;
561 x += dx2;
562 }
563 }
564
565 continue;
566 }
567 else if (_icon_algo == ARRANGE_BORDER_HV) {
568 if (++cnt & 1)
569 x = work_area.right - x;
570 else if (cnt & 2) {
571 y += dy1;
572
573 if (y >= work_area.bottom) {
574 y = start_y;
575 x += dx2;
576 }
577 } else {
578 x += dx1;
579
580 if (x >= work_area.right) {
581 x = start_x;
582 y += dy2;
583 }
584 }
585
586 continue;
587 }
588 else if (_icon_algo == ARRANGE_ROUNDABOUT) {
589
590 ///@todo
591
592 }
593
594 x += dx1;
595 y += dy1;
596
597 if (x<0 || x>=work_area.right) {
598 x = start_x;
599 y += dy2;
600 } else if (y<0 || y>=work_area.bottom) {
601 y = start_y;
602 x += dx2;
603 }
604 }
605
606 // use a little trick to get the icons where we want them to be...
607
608 for(IconMap::const_iterator it=pos_idx.end(); --it!=pos_idx.begin(); ) {
609 const IconPos& pos = it->first;
610
611 ListView_SetItemPosition32(_hwndListView, it->second, pos.second, pos.first);
612 }
613
614 for(IconMap::const_iterator it=pos_idx.begin(); it!=pos_idx.end(); ++it) {
615 const IconPos& pos = it->first;
616
617 ListView_SetItemPosition32(_hwndListView, it->second, pos.second, pos.first);
618 }
619 }