1 /*
2  * PROJECT:     PAINT for ReactOS
3  * LICENSE:     LGPL
4  * FILE:        base/applications/mspaint/history.cpp
5  * PURPOSE:     Undo and redo functionality
6  * PROGRAMMERS: Benedikt Freisen
7  */
8 
9 /* INCLUDES *********************************************************/
10 
11 #include "precomp.h"
12 
13 /* FUNCTIONS ********************************************************/
14 
15 void ImageModel::NotifyDimensionsChanged()
16 {
17     if (imageArea.IsWindow())
18         imageArea.SendMessage(WM_IMAGEMODELDIMENSIONSCHANGED);
19 }
20 
21 void ImageModel::NotifyImageChanged()
22 {
23     if (imageArea.IsWindow())
24         imageArea.SendMessage(WM_IMAGEMODELIMAGECHANGED);
25 }
26 
27 ImageModel::ImageModel()
28 {
29     currInd = 0;
30     undoSteps = 0;
31     redoSteps = 0;
32     imageSaved = TRUE;
33 
34     // prepare a minimal usable bitmap
35     int imgXRes = 1;
36     int imgYRes = 1;
37 
38     hDrawingDC = CreateCompatibleDC(NULL);
39     SelectObject(hDrawingDC, CreatePen(PS_SOLID, 0, paletteModel.GetFgColor()));
40     SelectObject(hDrawingDC, CreateSolidBrush(paletteModel.GetBgColor()));
41 
42     hBms[0] = CreateDIBWithProperties(imgXRes, imgYRes);
43     SelectObject(hDrawingDC, hBms[0]);
44     Rectangle(hDrawingDC, 0 - 1, 0 - 1, imgXRes + 1, imgYRes + 1);
45 }
46 
47 void ImageModel::CopyPrevious()
48 {
49     ATLTRACE("%s: %d\n", __FUNCTION__, currInd);
50     DeleteObject(hBms[(currInd + 1) % HISTORYSIZE]);
51     hBms[(currInd + 1) % HISTORYSIZE] = CopyDIBImage(hBms[currInd]);
52     currInd = (currInd + 1) % HISTORYSIZE;
53     if (undoSteps < HISTORYSIZE - 1)
54         undoSteps++;
55     redoSteps = 0;
56     SelectObject(hDrawingDC, hBms[currInd]);
57     imageSaved = FALSE;
58 }
59 
60 void ImageModel::Undo(BOOL bClearRedo)
61 {
62     ATLTRACE("%s: %d\n", __FUNCTION__, undoSteps);
63     if (undoSteps > 0)
64     {
65         int oldWidth = GetWidth();
66         int oldHeight = GetHeight();
67         selectionWindow.ShowWindow(SW_HIDE);
68         currInd = (currInd + HISTORYSIZE - 1) % HISTORYSIZE;
69         SelectObject(hDrawingDC, hBms[currInd]);
70         undoSteps--;
71         if (bClearRedo)
72             redoSteps = 0;
73         else if (redoSteps < HISTORYSIZE - 1)
74             redoSteps++;
75         if (GetWidth() != oldWidth || GetHeight() != oldHeight)
76             NotifyDimensionsChanged();
77         NotifyImageChanged();
78     }
79 }
80 
81 void ImageModel::Redo()
82 {
83     ATLTRACE("%s: %d\n", __FUNCTION__, redoSteps);
84     if (redoSteps > 0)
85     {
86         int oldWidth = GetWidth();
87         int oldHeight = GetHeight();
88         selectionWindow.ShowWindow(SW_HIDE);
89         currInd = (currInd + 1) % HISTORYSIZE;
90         SelectObject(hDrawingDC, hBms[currInd]);
91         redoSteps--;
92         if (undoSteps < HISTORYSIZE - 1)
93             undoSteps++;
94         if (GetWidth() != oldWidth || GetHeight() != oldHeight)
95             NotifyDimensionsChanged();
96         NotifyImageChanged();
97     }
98 }
99 
100 void ImageModel::ResetToPrevious()
101 {
102     ATLTRACE("%s: %d\n", __FUNCTION__, currInd);
103     DeleteObject(hBms[currInd]);
104     hBms[currInd] = CopyDIBImage(hBms[(currInd + HISTORYSIZE - 1) % HISTORYSIZE]);
105     SelectObject(hDrawingDC, hBms[currInd]);
106     NotifyImageChanged();
107 }
108 
109 void ImageModel::ClearHistory()
110 {
111     undoSteps = 0;
112     redoSteps = 0;
113 }
114 
115 void ImageModel::Insert(HBITMAP hbm)
116 {
117     int oldWidth = GetWidth();
118     int oldHeight = GetHeight();
119     DeleteObject(hBms[(currInd + 1) % HISTORYSIZE]);
120     hBms[(currInd + 1) % HISTORYSIZE] = hbm;
121     currInd = (currInd + 1) % HISTORYSIZE;
122     if (undoSteps < HISTORYSIZE - 1)
123         undoSteps++;
124     redoSteps = 0;
125     SelectObject(hDrawingDC, hBms[currInd]);
126     if (GetWidth() != oldWidth || GetHeight() != oldHeight)
127         NotifyDimensionsChanged();
128     NotifyImageChanged();
129 }
130 
131 void ImageModel::Crop(int nWidth, int nHeight, int nOffsetX, int nOffsetY)
132 {
133     HDC hdc;
134     HPEN oldPen;
135     HBRUSH oldBrush;
136     int oldWidth = GetWidth();
137     int oldHeight = GetHeight();
138 
139     if (nWidth <= 0)
140         nWidth = 1;
141     if (nHeight <= 0)
142         nHeight = 1;
143 
144     SelectObject(hDrawingDC, hBms[currInd]);
145     DeleteObject(hBms[(currInd + 1) % HISTORYSIZE]);
146     hBms[(currInd + 1) % HISTORYSIZE] = CreateDIBWithProperties(nWidth, nHeight);
147     currInd = (currInd + 1) % HISTORYSIZE;
148     if (undoSteps < HISTORYSIZE - 1)
149         undoSteps++;
150     redoSteps = 0;
151 
152     hdc = CreateCompatibleDC(hDrawingDC);
153     SelectObject(hdc, hBms[currInd]);
154 
155     oldPen = (HPEN) SelectObject(hdc, CreatePen(PS_SOLID, 1, paletteModel.GetBgColor()));
156     oldBrush = (HBRUSH) SelectObject(hdc, CreateSolidBrush(paletteModel.GetBgColor()));
157     Rectangle(hdc, 0, 0, nWidth, nHeight);
158     BitBlt(hdc, -nOffsetX, -nOffsetY, GetWidth(), GetHeight(), hDrawingDC, 0, 0, SRCCOPY);
159     DeleteObject(SelectObject(hdc, oldBrush));
160     DeleteObject(SelectObject(hdc, oldPen));
161     DeleteDC(hdc);
162     SelectObject(hDrawingDC, hBms[currInd]);
163 
164     if (GetWidth() != oldWidth || GetHeight() != oldHeight)
165         NotifyDimensionsChanged();
166     NotifyImageChanged();
167 }
168 
169 void ImageModel::SaveImage(LPTSTR lpFileName)
170 {
171     SaveDIBToFile(hBms[currInd], lpFileName, hDrawingDC);
172 }
173 
174 BOOL ImageModel::IsImageSaved() const
175 {
176     return imageSaved;
177 }
178 
179 BOOL ImageModel::HasUndoSteps() const
180 {
181     return undoSteps > 0;
182 }
183 
184 BOOL ImageModel::HasRedoSteps() const
185 {
186     return redoSteps > 0;
187 }
188 
189 void ImageModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY)
190 {
191     int oldWidth = GetWidth();
192     int oldHeight = GetHeight();
193     INT newWidth = oldWidth * nStretchPercentX / 100;
194     INT newHeight = oldHeight * nStretchPercentY / 100;
195     if (oldWidth != newWidth || oldHeight != newHeight)
196     {
197         HBITMAP hbm0 = CopyDIBImage(hBms[currInd], newWidth, newHeight);
198         Insert(hbm0);
199     }
200     if (nSkewDegX)
201     {
202         HBITMAP hbm1 = SkewDIB(hDrawingDC, hBms[currInd], nSkewDegX, FALSE);
203         Insert(hbm1);
204     }
205     if (nSkewDegY)
206     {
207         HBITMAP hbm2 = SkewDIB(hDrawingDC, hBms[currInd], nSkewDegY, TRUE);
208         Insert(hbm2);
209     }
210     if (GetWidth() != oldWidth || GetHeight() != oldHeight)
211         NotifyDimensionsChanged();
212     NotifyImageChanged();
213 }
214 
215 int ImageModel::GetWidth() const
216 {
217     return GetDIBWidth(hBms[currInd]);
218 }
219 
220 int ImageModel::GetHeight() const
221 {
222     return GetDIBHeight(hBms[currInd]);
223 }
224 
225 void ImageModel::InvertColors()
226 {
227     RECT rect = {0, 0, GetWidth(), GetHeight()};
228     CopyPrevious();
229     InvertRect(hDrawingDC, &rect);
230     NotifyImageChanged();
231 }
232 
233 void ImageModel::Clear(COLORREF color)
234 {
235     Rectangle(hDrawingDC, 0 - 1, 0 - 1, GetWidth() + 1, GetHeight() + 1);
236     NotifyImageChanged();
237 }
238 
239 HDC ImageModel::GetDC()
240 {
241     return hDrawingDC;
242 }
243 
244 void ImageModel::FlipHorizontally()
245 {
246     CopyPrevious();
247     StretchBlt(hDrawingDC, GetWidth() - 1, 0, -GetWidth(), GetHeight(), GetDC(), 0, 0,
248                GetWidth(), GetHeight(), SRCCOPY);
249     NotifyImageChanged();
250 }
251 
252 void ImageModel::FlipVertically()
253 {
254     CopyPrevious();
255     StretchBlt(hDrawingDC, 0, GetHeight() - 1, GetWidth(), -GetHeight(), GetDC(), 0, 0,
256                GetWidth(), GetHeight(), SRCCOPY);
257     NotifyImageChanged();
258 }
259 
260 void ImageModel::RotateNTimes90Degrees(int iN)
261 {
262     switch (iN)
263     {
264     case 1:
265     case 3:
266         DeleteObject(hBms[(currInd + 1) % HISTORYSIZE]);
267         hBms[(currInd + 1) % HISTORYSIZE] = Rotate90DegreeBlt(hDrawingDC, GetWidth(), GetHeight(), iN == 1);
268         currInd = (currInd + 1) % HISTORYSIZE;
269         if (undoSteps < HISTORYSIZE - 1)
270             undoSteps++;
271         redoSteps = 0;
272         SelectObject(hDrawingDC, hBms[currInd]);
273         imageSaved = FALSE;
274         NotifyDimensionsChanged();
275         break;
276     case 2:
277         CopyPrevious();
278         StretchBlt(hDrawingDC, GetWidth() - 1, GetHeight() - 1, -GetWidth(), -GetHeight(), GetDC(),
279                    0, 0, GetWidth(), GetHeight(), SRCCOPY);
280         break;
281     }
282     NotifyImageChanged();
283 }
284 
285 void ImageModel::DrawSelectionBackground(COLORREF rgbBG)
286 {
287     if (toolsModel.GetActiveTool() == TOOL_FREESEL)
288         selectionModel.DrawBackgroundPoly(hDrawingDC, rgbBG);
289     else
290         selectionModel.DrawBackgroundRect(hDrawingDC, rgbBG);
291 }
292 
293 void ImageModel::DeleteSelection()
294 {
295     if (selectionWindow.IsWindowVisible())
296         ResetToPrevious();
297     CopyPrevious();
298     if (selectionWindow.IsWindowVisible())
299         Undo(TRUE);
300     DrawSelectionBackground(paletteModel.GetBgColor());
301     selectionWindow.ShowWindow(SW_HIDE);
302     NotifyImageChanged();
303 }
304 
305 void ImageModel::Bound(POINT& pt)
306 {
307     pt.x = max(0, min(pt.x, GetWidth()));
308     pt.y = max(0, min(pt.y, GetHeight()));
309 }
310