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