jtkorhonen@0
|
1 //** Copyright (C) Jari Korhonen, 2010 (under lgpl)
|
jtkorhonen@0
|
2
|
jtkorhonen@0
|
3 #include <QtGui>
|
jtkorhonen@0
|
4
|
jtkorhonen@0
|
5 #include "hgexpwidget.h"
|
jtkorhonen@0
|
6 #include "common.h"
|
jtkorhonen@0
|
7
|
jtkorhonen@0
|
8 #define REMOTE_REPO_STR "Remote repository: "
|
jtkorhonen@0
|
9 #define LOCAL_REPO_STR "Local repository: "
|
jtkorhonen@0
|
10 #define WORKFOLDER_STR "Working folder: "
|
jtkorhonen@0
|
11
|
jtkorhonen@0
|
12
|
jtkorhonen@0
|
13 const char hgStatViewOptions[NUM_STAT_FILE_TYPES] = {'m','a','r','d','u','c','i'};
|
jtkorhonen@0
|
14
|
jtkorhonen@0
|
15 const char *statFilesStr[NUM_STAT_FILE_TYPES] = { "M: Modified",
|
jtkorhonen@0
|
16 "A: To be added on next commit",
|
jtkorhonen@0
|
17 "R: To be removed on next commit",
|
jtkorhonen@0
|
18 "!: Tracked, locally deleted",
|
jtkorhonen@0
|
19 "?: Unknown, not yet tracked",
|
jtkorhonen@0
|
20 "C: Clean (not changed)",
|
jtkorhonen@0
|
21 "I: Ignored (via .hgignore file)"};
|
jtkorhonen@0
|
22
|
jtkorhonen@0
|
23
|
jtkorhonen@0
|
24 HgExpWidget::HgExpWidget(QWidget *parent, QString remoteRepo, QString workFolderPath, unsigned char viewFileTypesBits): QTabWidget(parent)
|
jtkorhonen@0
|
25 {
|
jtkorhonen@0
|
26 //Work page
|
jtkorhonen@0
|
27 //Work page
|
jtkorhonen@0
|
28 //Work page
|
jtkorhonen@0
|
29
|
jtkorhonen@0
|
30 //Remote repo
|
jtkorhonen@0
|
31 grpRemoteRepo = new QGroupBox(tr(REMOTE_REPO_STR) + remoteRepo);
|
jtkorhonen@0
|
32 grpRemoteRepo -> setMinimumHeight(24);
|
jtkorhonen@0
|
33
|
jtkorhonen@0
|
34 //Local Repo
|
jtkorhonen@0
|
35 grpLocalRepo = new QGroupBox(tr(LOCAL_REPO_STR) + workFolderPath + getHgDirName());
|
jtkorhonen@0
|
36 parentsLabel = new QLabel(tr("Working folder parent(s):"));
|
jtkorhonen@0
|
37 localRepoHgParentsList = new QListWidget;
|
jtkorhonen@0
|
38 localRepoHgParentsList -> setSelectionMode(QAbstractItemView::NoSelection);
|
jtkorhonen@0
|
39 parentsLayout = new QVBoxLayout;
|
jtkorhonen@0
|
40 parentsLayout -> addWidget(parentsLabel);
|
jtkorhonen@0
|
41 parentsLayout -> addWidget(localRepoHgParentsList);
|
jtkorhonen@0
|
42 grpLocalRepo -> setLayout(parentsLayout);
|
jtkorhonen@0
|
43
|
jtkorhonen@0
|
44 //Workfolder
|
jtkorhonen@0
|
45 grpWorkFolder = new QGroupBox(tr(WORKFOLDER_STR) + workFolderPath);
|
jtkorhonen@0
|
46 workFolderLayout = new QHBoxLayout;
|
jtkorhonen@0
|
47 workFolderFileList = new QListWidget;
|
jtkorhonen@19
|
48 workFolderFileList -> setSelectionMode(QAbstractItemView::ExtendedSelection);
|
jtkorhonen@0
|
49 grpViewFileTypes = new QGroupBox;
|
jtkorhonen@0
|
50 fileTypesLayout = new QVBoxLayout;
|
jtkorhonen@0
|
51
|
jtkorhonen@0
|
52 for(int i = 0; i < NUM_STAT_FILE_TYPES; i++)
|
jtkorhonen@0
|
53 {
|
jtkorhonen@0
|
54 chkViewFileTypes[i] = new QCheckBox(statFilesStr[i]);
|
jtkorhonen@0
|
55 if ((1U << i) & viewFileTypesBits)
|
jtkorhonen@0
|
56 {
|
jtkorhonen@0
|
57 chkViewFileTypes[i]->setChecked(true);
|
jtkorhonen@0
|
58 }
|
jtkorhonen@0
|
59 else
|
jtkorhonen@0
|
60 {
|
jtkorhonen@0
|
61 chkViewFileTypes[i]->setChecked(false);
|
jtkorhonen@0
|
62 }
|
jtkorhonen@0
|
63 connect(chkViewFileTypes[i], SIGNAL(stateChanged(int)), this, SIGNAL(workFolderViewTypesChanged()));
|
jtkorhonen@0
|
64 fileTypesLayout -> addWidget(chkViewFileTypes[i]);
|
jtkorhonen@0
|
65 }
|
jtkorhonen@0
|
66
|
jtkorhonen@0
|
67 grpViewFileTypes -> setLayout(fileTypesLayout);
|
jtkorhonen@0
|
68 workFolderLayout->addWidget(workFolderFileList, 3);
|
jtkorhonen@0
|
69 workFolderLayout->addWidget(grpViewFileTypes, 1);
|
jtkorhonen@0
|
70 grpWorkFolder -> setLayout(workFolderLayout);
|
jtkorhonen@0
|
71
|
jtkorhonen@0
|
72 workPageWidget = new QWidget;
|
jtkorhonen@0
|
73 mainLayout = new QVBoxLayout(workPageWidget);
|
jtkorhonen@0
|
74 mainLayout -> addWidget(grpRemoteRepo, 1);
|
jtkorhonen@0
|
75 mainLayout -> addWidget(grpLocalRepo, 8);
|
jtkorhonen@0
|
76 mainLayout -> addWidget(grpWorkFolder, 12);
|
jtkorhonen@0
|
77 addTab(workPageWidget, tr("Work"));
|
jtkorhonen@0
|
78
|
jtkorhonen@0
|
79 //History page
|
jtkorhonen@0
|
80 //History page
|
jtkorhonen@0
|
81 //History page
|
jtkorhonen@0
|
82 historyPageWidget = new QWidget;
|
jtkorhonen@0
|
83 localRepoHgLogList = new QListWidget;
|
jtkorhonen@0
|
84 localRepoHgLogList->setFont(QFont("Courier New"));
|
jtkorhonen@0
|
85 localRepoHgLogList -> setSelectionMode(QAbstractItemView::ExtendedSelection);
|
jtkorhonen@0
|
86
|
jtkorhonen@0
|
87 historyLayout = new QVBoxLayout(historyPageWidget);
|
jtkorhonen@0
|
88 historyLayout->addWidget(localRepoHgLogList);
|
jtkorhonen@0
|
89 addTab(historyPageWidget, tr("History (log)"));
|
jtkorhonen@0
|
90
|
jtkorhonen@0
|
91 //Heads page
|
jtkorhonen@0
|
92 //Heads page
|
jtkorhonen@0
|
93 //Heads page
|
jtkorhonen@0
|
94 headsPageWidget = new QWidget;
|
jtkorhonen@0
|
95 localRepoHeadsList = new QListWidget;
|
jtkorhonen@0
|
96 localRepoHeadsList -> setSelectionMode(QAbstractItemView::ExtendedSelection);
|
jtkorhonen@0
|
97
|
jtkorhonen@0
|
98 headsLayout = new QVBoxLayout(headsPageWidget);
|
jtkorhonen@0
|
99 headsLayout->addWidget(localRepoHeadsList);
|
jtkorhonen@0
|
100 addTab(headsPageWidget, tr("Heads"));
|
jtkorhonen@0
|
101
|
jtkorhonen@0
|
102 //Initially, only work page is active
|
jtkorhonen@0
|
103 setTabEnabled(HEADSTAB, false);
|
jtkorhonen@0
|
104 setTabEnabled(HISTORYTAB, false);
|
jtkorhonen@0
|
105 }
|
jtkorhonen@0
|
106
|
jtkorhonen@0
|
107
|
jtkorhonen@0
|
108 QString HgExpWidget::getStatFlags()
|
jtkorhonen@0
|
109 {
|
jtkorhonen@0
|
110 QString ret;
|
jtkorhonen@0
|
111
|
jtkorhonen@0
|
112 for(int i = 0; i < NUM_STAT_FILE_TYPES; i++)
|
jtkorhonen@0
|
113 {
|
jtkorhonen@0
|
114 if (Qt::Checked == chkViewFileTypes[i]->checkState())
|
jtkorhonen@0
|
115 {
|
jtkorhonen@0
|
116 ret += hgStatViewOptions[i];
|
jtkorhonen@0
|
117 }
|
jtkorhonen@0
|
118 }
|
jtkorhonen@0
|
119
|
jtkorhonen@0
|
120 return ret;
|
jtkorhonen@0
|
121 }
|
jtkorhonen@0
|
122
|
jtkorhonen@0
|
123
|
jtkorhonen@0
|
124 unsigned char HgExpWidget::getFileTypesBits()
|
jtkorhonen@0
|
125 {
|
jtkorhonen@0
|
126 unsigned char ret;
|
jtkorhonen@0
|
127
|
jtkorhonen@0
|
128 ret = 0;
|
jtkorhonen@0
|
129
|
jtkorhonen@0
|
130 for(int i = 0; i < NUM_STAT_FILE_TYPES; i++)
|
jtkorhonen@0
|
131 {
|
jtkorhonen@0
|
132 if (Qt::Checked == chkViewFileTypes[i]->checkState())
|
jtkorhonen@0
|
133 {
|
jtkorhonen@0
|
134 ret |= (1U << i);
|
jtkorhonen@0
|
135 }
|
jtkorhonen@0
|
136 }
|
jtkorhonen@0
|
137
|
jtkorhonen@0
|
138 return ret;
|
jtkorhonen@0
|
139 }
|
jtkorhonen@0
|
140
|
jtkorhonen@0
|
141
|
jtkorhonen@0
|
142 void HgExpWidget::updateWorkFolderFileList(QString fileList)
|
jtkorhonen@0
|
143 {
|
jtkorhonen@0
|
144 workFolderFileList-> clear();
|
jtkorhonen@0
|
145 workFolderFileList -> addItems(fileList.split("\n"));
|
jtkorhonen@0
|
146 }
|
jtkorhonen@0
|
147
|
jtkorhonen@0
|
148 void HgExpWidget::updateLocalRepoHeadsList(QString headList)
|
jtkorhonen@0
|
149 {
|
jtkorhonen@0
|
150 localRepoHeadsList-> clear();
|
jtkorhonen@0
|
151 localRepoHeadsList -> addItems(splitChangeSets(headList));
|
jtkorhonen@0
|
152
|
jtkorhonen@0
|
153 //heads list is interesting only when we have 2 or more
|
jtkorhonen@0
|
154 if (localRepoHeadsList-> count() < 2)
|
jtkorhonen@0
|
155 {
|
jtkorhonen@0
|
156 setTabEnabled(HEADSTAB, false);
|
jtkorhonen@0
|
157 }
|
jtkorhonen@0
|
158 else
|
jtkorhonen@0
|
159 {
|
jtkorhonen@0
|
160 setTabEnabled(HEADSTAB, true);
|
jtkorhonen@0
|
161 }
|
jtkorhonen@0
|
162 }
|
jtkorhonen@0
|
163
|
jtkorhonen@0
|
164
|
jtkorhonen@0
|
165 void HgExpWidget::clearLists()
|
jtkorhonen@0
|
166 {
|
jtkorhonen@0
|
167 localRepoHeadsList-> clear();
|
jtkorhonen@0
|
168 localRepoHgParentsList-> clear();
|
jtkorhonen@0
|
169 workFolderFileList-> clear();
|
jtkorhonen@0
|
170 localRepoHgLogList -> clear();
|
jtkorhonen@0
|
171 }
|
jtkorhonen@0
|
172
|
jtkorhonen@0
|
173 void HgExpWidget::updateLocalRepoParentsList(QString parentsList)
|
jtkorhonen@0
|
174 {
|
jtkorhonen@0
|
175 localRepoHgParentsList-> clear();
|
jtkorhonen@0
|
176 localRepoHgParentsList -> addItems(splitChangeSets(parentsList));
|
jtkorhonen@0
|
177 }
|
jtkorhonen@0
|
178
|
jtkorhonen@0
|
179 void HgExpWidget::updateLocalRepoHgLogList(QString hgLogList)
|
jtkorhonen@0
|
180 {
|
jtkorhonen@0
|
181 localRepoHgLogList -> clear();
|
jtkorhonen@0
|
182 localRepoHgLogList -> addItems(splitChangeSets(hgLogList));
|
jtkorhonen@0
|
183
|
jtkorhonen@0
|
184 }
|
jtkorhonen@0
|
185
|
jtkorhonen@0
|
186
|
jtkorhonen@0
|
187 int HgExpWidget::findLineStart(int nowIndex, QString str)
|
jtkorhonen@0
|
188 {
|
jtkorhonen@0
|
189 if (nowIndex < 0)
|
jtkorhonen@0
|
190 {
|
jtkorhonen@0
|
191 return -1;
|
jtkorhonen@0
|
192 }
|
jtkorhonen@0
|
193
|
jtkorhonen@0
|
194 while(str.at(nowIndex) != '\n')
|
jtkorhonen@0
|
195 {
|
jtkorhonen@0
|
196 if (nowIndex == 0)
|
jtkorhonen@0
|
197 {
|
jtkorhonen@0
|
198 return nowIndex;
|
jtkorhonen@0
|
199 }
|
jtkorhonen@0
|
200 nowIndex--;
|
jtkorhonen@0
|
201 }
|
jtkorhonen@0
|
202 return nowIndex + 1;
|
jtkorhonen@0
|
203 }
|
jtkorhonen@0
|
204
|
jtkorhonen@0
|
205
|
jtkorhonen@0
|
206 QStringList HgExpWidget::splitChangeSets(QString chgSetsStr)
|
jtkorhonen@0
|
207 {
|
jtkorhonen@0
|
208 int currChgSet;
|
jtkorhonen@0
|
209 int currChgSetLineStart;
|
jtkorhonen@0
|
210
|
jtkorhonen@0
|
211 int prevChgSet;
|
jtkorhonen@0
|
212 QStringList tmp;
|
jtkorhonen@0
|
213
|
jtkorhonen@0
|
214 currChgSet = chgSetsStr.indexOf(CHGSET);
|
jtkorhonen@0
|
215 currChgSetLineStart = findLineStart(currChgSet, chgSetsStr);
|
jtkorhonen@0
|
216 prevChgSet = -1;
|
jtkorhonen@0
|
217 while (currChgSet != -1)
|
jtkorhonen@0
|
218 {
|
jtkorhonen@0
|
219 if (prevChgSet != -1)
|
jtkorhonen@0
|
220 {
|
jtkorhonen@0
|
221 tmp.append(chgSetsStr.mid(prevChgSet, (currChgSetLineStart - prevChgSet - 1)));
|
jtkorhonen@0
|
222 }
|
jtkorhonen@0
|
223
|
jtkorhonen@0
|
224 prevChgSet = currChgSetLineStart;
|
jtkorhonen@0
|
225
|
jtkorhonen@0
|
226 currChgSet = chgSetsStr.indexOf(CHGSET, currChgSet + 1);
|
jtkorhonen@0
|
227 currChgSetLineStart = findLineStart(currChgSet, chgSetsStr);
|
jtkorhonen@0
|
228 }
|
jtkorhonen@0
|
229
|
jtkorhonen@0
|
230 if (prevChgSet != -1)
|
jtkorhonen@0
|
231 {
|
jtkorhonen@0
|
232 //Last changeset
|
jtkorhonen@0
|
233 tmp.append(chgSetsStr.mid(prevChgSet));
|
jtkorhonen@0
|
234 }
|
jtkorhonen@0
|
235 else
|
jtkorhonen@0
|
236 {
|
jtkorhonen@0
|
237 //Only changeset (if any)
|
jtkorhonen@0
|
238 if (!chgSetsStr.isEmpty())
|
jtkorhonen@0
|
239 {
|
jtkorhonen@0
|
240 tmp.append(chgSetsStr.mid(0));
|
jtkorhonen@0
|
241 }
|
jtkorhonen@0
|
242 }
|
jtkorhonen@0
|
243
|
jtkorhonen@0
|
244 return tmp;
|
jtkorhonen@0
|
245 }
|
jtkorhonen@0
|
246
|
jtkorhonen@0
|
247 QString HgExpWidget::getCurrentFileListLine()
|
jtkorhonen@0
|
248 {
|
jtkorhonen@0
|
249 if (workFolderFileList -> currentItem() != NULL)
|
jtkorhonen@0
|
250 {
|
jtkorhonen@0
|
251 return workFolderFileList -> currentItem()->text();
|
jtkorhonen@0
|
252 }
|
jtkorhonen@0
|
253 return "";
|
jtkorhonen@0
|
254 }
|
jtkorhonen@0
|
255
|
jtkorhonen@0
|
256 void HgExpWidget::setWorkFolderAndRepoNames(QString workFolderPath, QString remoteRepoPath)
|
jtkorhonen@0
|
257 {
|
jtkorhonen@0
|
258 grpRemoteRepo -> setTitle(tr(REMOTE_REPO_STR) + remoteRepoPath);
|
jtkorhonen@0
|
259 grpLocalRepo -> setTitle(tr(LOCAL_REPO_STR) + workFolderPath + getHgDirName());
|
jtkorhonen@0
|
260 grpWorkFolder -> setTitle(tr(WORKFOLDER_STR) + workFolderPath);
|
jtkorhonen@0
|
261 }
|
jtkorhonen@0
|
262
|
jtkorhonen@0
|
263 #define MERC_SHA1_MARKER_LEN 12
|
jtkorhonen@0
|
264 QString HgExpWidget::findRev(QString itemText, QString & smallRev)
|
jtkorhonen@0
|
265 {
|
jtkorhonen@0
|
266 QString tmp(itemText);
|
jtkorhonen@0
|
267 int i;
|
jtkorhonen@0
|
268 int j;
|
jtkorhonen@0
|
269
|
jtkorhonen@0
|
270 smallRev ="0";
|
jtkorhonen@0
|
271
|
jtkorhonen@0
|
272 i = tmp.indexOf(CHGSET);
|
jtkorhonen@0
|
273 if (i != -1)
|
jtkorhonen@0
|
274 {
|
jtkorhonen@0
|
275 j = i + 10;
|
jtkorhonen@0
|
276 i = tmp.indexOf(":", j); //xx:yyyyyy after changeset:
|
jtkorhonen@0
|
277
|
jtkorhonen@0
|
278 if (i != -1)
|
jtkorhonen@0
|
279 {
|
jtkorhonen@0
|
280 smallRev = tmp.mid(j, (i-j));
|
jtkorhonen@0
|
281 return tmp.mid(i+1, MERC_SHA1_MARKER_LEN);
|
jtkorhonen@0
|
282 }
|
jtkorhonen@0
|
283 }
|
jtkorhonen@0
|
284
|
jtkorhonen@0
|
285 return "";
|
jtkorhonen@0
|
286 }
|
jtkorhonen@0
|
287
|
jtkorhonen@0
|
288 void HgExpWidget::getHistoryDiffRevisions(QString& revA, QString& revB)
|
jtkorhonen@0
|
289 {
|
jtkorhonen@0
|
290 QList <QListWidgetItem *> histList = localRepoHgLogList->selectedItems();
|
jtkorhonen@0
|
291 QList <QListWidgetItem *> headList = localRepoHeadsList->selectedItems();
|
jtkorhonen@0
|
292
|
jtkorhonen@0
|
293 QString revATmp;
|
jtkorhonen@0
|
294 QString revBTmp;
|
jtkorhonen@0
|
295 QString smallRevA;
|
jtkorhonen@0
|
296 QString smallRevB;
|
jtkorhonen@0
|
297 QString txtA;
|
jtkorhonen@0
|
298 QString txtB;
|
jtkorhonen@0
|
299
|
jtkorhonen@0
|
300 if (histList.count() == REQUIRED_CHGSET_DIFF_COUNT)
|
jtkorhonen@0
|
301 {
|
jtkorhonen@0
|
302 txtA = histList.last()->text();
|
jtkorhonen@0
|
303 txtB = histList.first()->text();
|
jtkorhonen@0
|
304
|
jtkorhonen@0
|
305 }
|
jtkorhonen@0
|
306 else if (headList.count() == REQUIRED_CHGSET_DIFF_COUNT)
|
jtkorhonen@0
|
307 {
|
jtkorhonen@0
|
308 txtA = headList.last()->text();
|
jtkorhonen@0
|
309 txtB = headList.first()->text();
|
jtkorhonen@0
|
310 }
|
jtkorhonen@0
|
311 else
|
jtkorhonen@0
|
312 {
|
jtkorhonen@0
|
313 revA = "";
|
jtkorhonen@0
|
314 revB = "";
|
jtkorhonen@0
|
315 return;
|
jtkorhonen@0
|
316 }
|
jtkorhonen@0
|
317
|
jtkorhonen@0
|
318 revATmp = findRev(txtA, smallRevA);
|
jtkorhonen@0
|
319 revBTmp = findRev(txtB, smallRevB);
|
jtkorhonen@0
|
320
|
jtkorhonen@0
|
321 //Switch order according to repo small revision number (user can select items from list in "wrong" order)
|
jtkorhonen@0
|
322 if (smallRevB.toULongLong() > smallRevA.toULongLong())
|
jtkorhonen@0
|
323 {
|
jtkorhonen@0
|
324 revA = revATmp;
|
jtkorhonen@0
|
325 revB = revBTmp;
|
jtkorhonen@0
|
326 }
|
jtkorhonen@0
|
327 else
|
jtkorhonen@0
|
328 {
|
jtkorhonen@0
|
329 revA = revBTmp;
|
jtkorhonen@0
|
330 revB = revATmp;
|
jtkorhonen@0
|
331 }
|
jtkorhonen@0
|
332 }
|
jtkorhonen@0
|
333
|
jtkorhonen@0
|
334
|
jtkorhonen@0
|
335 void HgExpWidget::getUpdateToRevRevision(QString& rev)
|
jtkorhonen@0
|
336 {
|
jtkorhonen@0
|
337 QList <QListWidgetItem *> histList = localRepoHgLogList->selectedItems();
|
jtkorhonen@0
|
338 QString txt;
|
jtkorhonen@0
|
339 QString smallRev;
|
jtkorhonen@0
|
340
|
jtkorhonen@0
|
341
|
jtkorhonen@0
|
342 if (histList.count() == 1)
|
jtkorhonen@0
|
343 {
|
jtkorhonen@0
|
344 txt = histList.first()->text();
|
jtkorhonen@0
|
345 rev = findRev(txt, smallRev);
|
jtkorhonen@0
|
346 }
|
jtkorhonen@0
|
347 else
|
jtkorhonen@0
|
348 {
|
jtkorhonen@0
|
349 rev = "";
|
jtkorhonen@0
|
350 }
|
jtkorhonen@0
|
351 }
|
jtkorhonen@0
|
352
|
jtkorhonen@0
|
353
|
jtkorhonen@0
|
354
|
jtkorhonen@0
|
355 void HgExpWidget::enableDisableOtherTabs()
|
jtkorhonen@0
|
356 {
|
jtkorhonen@0
|
357 //history list is only interesting when we have something in it ;-)
|
jtkorhonen@0
|
358 if (localRepoHgLogList -> count() < 2)
|
jtkorhonen@0
|
359 {
|
jtkorhonen@0
|
360 setTabEnabled(HISTORYTAB, false);
|
jtkorhonen@0
|
361 }
|
jtkorhonen@0
|
362 else
|
jtkorhonen@0
|
363 {
|
jtkorhonen@0
|
364 setTabEnabled(HISTORYTAB, true);
|
jtkorhonen@0
|
365 }
|
jtkorhonen@0
|
366
|
jtkorhonen@0
|
367 //history list is only interesting when we have something in it ;-)
|
jtkorhonen@0
|
368 if (localRepoHgLogList -> count() < 2)
|
jtkorhonen@0
|
369 {
|
jtkorhonen@0
|
370 setTabEnabled(HISTORYTAB, false);
|
jtkorhonen@0
|
371 }
|
jtkorhonen@0
|
372 else
|
jtkorhonen@0
|
373 {
|
jtkorhonen@0
|
374 setTabEnabled(HISTORYTAB, true);
|
jtkorhonen@0
|
375 }
|
jtkorhonen@0
|
376 }
|
jtkorhonen@0
|
377
|
jtkorhonen@0
|
378
|
jtkorhonen@0
|
379
|
jtkorhonen@0
|
380
|