Parse preferences file as UTF-8
[skype-call-recorder.git] / preferences.cpp
blobe42f49a315c11cdc02cb11870bcd273ea76f0a1f
1 /*
2 Skype Call Recorder
3 Copyright (C) 2008 jlh (jlh at gmx dot ch)
5 This program is free software; you can redistribute it and/or modify it
6 under the terms of the GNU General Public License as published by the
7 Free Software Foundation; either version 2 of the License, version 3 of
8 the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 The GNU General Public License version 2 is included with the source of
20 this program under the file name COPYING. You can also get a copy on
21 http://www.fsf.org/
24 #include <QVBoxLayout>
25 #include <QHBoxLayout>
26 #include <QGroupBox>
27 #include <QRadioButton>
28 #include <QLabel>
29 #include <QPushButton>
30 #include <QListView>
31 #include <QPair>
32 #include <QFile>
33 #include <QSet>
34 #include <QTextStream>
35 #include <QtAlgorithms>
36 #include <QDir>
37 #include <QDateTime>
38 #include <QList>
39 #include <QFileIconProvider>
40 #include <QFileDialog>
41 #include <QTabWidget>
42 #include <ctime>
44 #include "preferences.h"
45 #include "smartwidgets.h"
46 #include "common.h"
47 #include "recorder.h"
49 Preferences preferences;
51 QString getOutputPath() {
52 QString path = preferences.get(Pref::OutputPath).toString();
53 if (path.startsWith("~/") || path == "~")
54 path.replace(0, 1, QDir::homePath());
55 else if (!path.startsWith('/'))
56 path.prepend(QDir::currentPath() + '/');
57 return path;
60 namespace {
61 QString escape(const QString &s) {
62 QString out = s;
63 out.replace('%', "%%");
64 out.replace('&', "&&");
65 return out;
69 QString getFileName(const QString &skypeName, const QString &displayName,
70 const QString &mySkypeName, const QString &myDisplayName, const QDateTime &timestamp, const QString &pattern)
72 QString fileName;
73 if (pattern.isEmpty())
74 fileName = preferences.get(Pref::OutputPattern).toString();
75 else
76 fileName = pattern;
78 fileName.replace("&s", escape(skypeName));
79 fileName.replace("&d", escape(displayName));
80 fileName.replace("&t", escape(mySkypeName));
81 fileName.replace("&e", escape(myDisplayName));
82 fileName.replace("&&", "&");
84 // TODO: uhm, does QT provide any time formatting the strftime() way?
85 char *buf = new char[fileName.size() + 1024];
86 time_t t = timestamp.toTime_t();
87 struct tm *tm = std::localtime(&t);
88 std::strftime(buf, fileName.size() + 1024, fileName.toUtf8().constData(), tm);
89 fileName = buf;
90 delete[] buf;
92 return getOutputPath() + '/' + fileName;
95 // preferences dialog
97 static QVBoxLayout *makeVFrame(QVBoxLayout *parentLayout, const char *title) {
98 QGroupBox *box = new QGroupBox(title);
99 QVBoxLayout *vbox = new QVBoxLayout(box);
100 parentLayout->addWidget(box);
101 return vbox;
104 QWidget *PreferencesDialog::createRecordingTab() {
105 QWidget *widget = new QWidget;
106 QVBoxLayout *vbox = new QVBoxLayout(widget);
108 Preference &preference = preferences.get(Pref::AutoRecordDefault);
109 SmartRadioButton *radio = new SmartRadioButton("Automatically &record all calls", preference, "yes");
110 vbox->addWidget(radio);
111 radio = new SmartRadioButton("&Ask for every call", preference, "ask");
112 vbox->addWidget(radio);
113 radio = new SmartRadioButton("Do &not automatically record calls", preference, "no");
114 vbox->addWidget(radio);
116 QPushButton *button = new QPushButton("Edit &per caller preferences");
117 connect(button, SIGNAL(clicked(bool)), this, SLOT(editPerCallerPreferences()));
118 vbox->addWidget(button);
120 SmartCheckBox *check = new SmartCheckBox("Show &balloon notification when recording starts", preferences.get(Pref::NotifyRecordingStart));
121 vbox->addWidget(check);
123 vbox->addStretch();
124 return widget;
127 QWidget *PreferencesDialog::createPathTab() {
128 QWidget *widget = new QWidget;
129 QVBoxLayout *vbox = new QVBoxLayout(widget);
131 QLabel *label = new QLabel("&Save recorded calls here:");
132 outputPathEdit = new SmartLineEdit(preferences.get(Pref::OutputPath));
133 label->setBuddy(outputPathEdit);
134 connect(outputPathEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateAbsolutePathWarning(const QString &)));
135 QPushButton *button = new QPushButton(QFileIconProvider().icon(QFileIconProvider::Folder), "Browse");
136 connect(button, SIGNAL(clicked(bool)), this, SLOT(browseOutputPath()));
137 QHBoxLayout *hbox = new QHBoxLayout;
138 hbox->addWidget(outputPathEdit);
139 hbox->addWidget(button);
140 vbox->addWidget(label);
141 vbox->addLayout(hbox);
143 label = new QLabel("File &name:");
144 patternWidget = new SmartEditableComboBox(preferences.get(Pref::OutputPattern));
145 label->setBuddy(patternWidget);
146 patternWidget->addItem("%Y-%m-%d %H:%M:%S Call with &s");
147 patternWidget->addItem("Call with &s, %a %b %d %Y, %H:%M:%S");
148 patternWidget->addItem("%Y, %B/Call with &s, %a %b %d %Y, %H:%M:%S");
149 patternWidget->addItem("Calls with &s/Call with &s, %a %b %d %Y, %H:%M:%S");
150 patternWidget->setupDone();
151 connect(patternWidget, SIGNAL(editTextChanged(const QString &)), this, SLOT(updatePatternToolTip(const QString &)));
152 vbox->addWidget(label);
153 vbox->addWidget(patternWidget);
155 vbox->addStretch();
157 absolutePathWarningLabel = new QLabel("<b>Warning:</b> The path you have entered is not an absolute path!");
158 vbox->addWidget(absolutePathWarningLabel);
160 updatePatternToolTip("");
161 updateAbsolutePathWarning(preferences.get(Pref::OutputPath).toString());
163 return widget;
166 QWidget *PreferencesDialog::createFormatTab() {
167 QWidget *widget = new QWidget;
168 QVBoxLayout *vbox = new QVBoxLayout(widget);
169 QGridLayout *grid = new QGridLayout;
171 QLabel *label = new QLabel("Fil&e format:");
172 formatWidget = new SmartComboBox(preferences.get(Pref::OutputFormat));
173 label->setBuddy(formatWidget);
174 formatWidget->addItem("WAV PCM", "wav");
175 formatWidget->addItem("MP3", "mp3");
176 formatWidget->addItem("Ogg Vorbis", "vorbis");
177 formatWidget->setupDone();
178 connect(formatWidget, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFormatSettings()));
179 grid->addWidget(label, 0, 0);
180 grid->addWidget(formatWidget, 0, 1);
182 label = new QLabel("MP3 &bitrate:");
183 SmartComboBox *combo = new SmartComboBox(preferences.get(Pref::OutputFormatMp3Bitrate));
184 label->setBuddy(combo);
185 combo->addItem("8 kbps", 8);
186 combo->addItem("16 kbps", 16);
187 combo->addItem("24 kbps", 24);
188 combo->addItem("32 kbps (recommended for mono)", 32);
189 combo->addItem("40 kbps", 40);
190 combo->addItem("48 kbps", 48);
191 combo->addItem("56 kbps", 56);
192 combo->addItem("64 kbps (recommended for stereo)", 64);
193 combo->addItem("80 kbps", 80);
194 combo->addItem("96 kbps", 96);
195 combo->addItem("112 kbps", 112);
196 combo->addItem("128 kbps", 128);
197 combo->addItem("144 kbps", 144);
198 combo->addItem("160 kbps", 160);
199 combo->setupDone();
200 mp3Settings.append(label);
201 mp3Settings.append(combo);
202 grid->addWidget(label, 1, 0);
203 grid->addWidget(combo, 1, 1);
205 label = new QLabel("Ogg Vorbis &quality:");
206 combo = new SmartComboBox(preferences.get(Pref::OutputFormatVorbisQuality));
207 label->setBuddy(combo);
208 combo->addItem("Quality -1", -1);
209 combo->addItem("Quality 0", 0);
210 combo->addItem("Quality 1", 1);
211 combo->addItem("Quality 2", 2);
212 combo->addItem("Quality 3 (recommended)", 3);
213 combo->addItem("Quality 4", 4);
214 combo->addItem("Quality 5", 5);
215 combo->addItem("Quality 6", 6);
216 combo->addItem("Quality 7", 7);
217 combo->addItem("Quality 8", 8);
218 combo->addItem("Quality 9", 9);
219 combo->addItem("Quality 10", 10);
220 combo->setupDone();
221 vorbisSettings.append(label);
222 vorbisSettings.append(combo);
223 grid->addWidget(label, 2, 0);
224 grid->addWidget(combo, 2, 1);
226 vbox->addLayout(grid);
228 SmartCheckBox *check = new SmartCheckBox("Save to &stereo file", preferences.get(Pref::OutputStereo));
229 connect(check, SIGNAL(clicked(bool)), this, SLOT(updateStereoSettings(bool)));
230 vbox->addWidget(check);
232 stereoMixLabel = new QLabel("");
233 SmartSlider *slider = new SmartSlider(preferences.get(Pref::OutputStereoMix));
234 stereoMixLabel->setBuddy(slider);
235 slider->setOrientation(Qt::Horizontal);
236 slider->setRange(0, 100);
237 slider->setSingleStep(1);
238 slider->setPageStep(10);
239 slider->setTickPosition(QSlider::TicksBelow);
240 slider->setTickInterval(10);
241 slider->setupDone();
242 connect(slider, SIGNAL(valueChanged(int)), this, SLOT(updateStereoMixLabel(int)));
243 stereoSettings.append(stereoMixLabel);
244 stereoSettings.append(slider);
245 vbox->addWidget(stereoMixLabel);
246 vbox->addWidget(slider);
248 check = new SmartCheckBox("Save call &information in files", preferences.get(Pref::OutputSaveTags));
249 mp3Settings.append(check);
250 vorbisSettings.append(check);
251 vbox->addWidget(check);
253 vbox->addStretch();
254 updateFormatSettings();
255 updateStereoSettings(preferences.get(Pref::OutputStereo).toBool());
256 updateStereoMixLabel(preferences.get(Pref::OutputStereoMix).toInt());
257 return widget;
260 QWidget *PreferencesDialog::createMiscTab() {
261 QWidget *widget = new QWidget;
262 QVBoxLayout *vbox = new QVBoxLayout(widget);
264 SmartCheckBox *check = new SmartCheckBox("&Display a small main window. Enable this if your\n"
265 "environment does not provide a system tray (needs restart)", preferences.get(Pref::GuiWindowed));
266 vbox->addWidget(check);
268 vbox->addStretch();
269 return widget;
272 PreferencesDialog::PreferencesDialog() {
273 setWindowTitle(PROGRAM_NAME " - Preferences");
274 setAttribute(Qt::WA_DeleteOnClose);
276 QVBoxLayout *vbox = new QVBoxLayout(this);
277 vbox->setSizeConstraint(QLayout::SetFixedSize);
279 QTabWidget *tabWidget = new QTabWidget;
280 vbox->addWidget(tabWidget);
282 tabWidget->addTab(createRecordingTab(), "Au&tomatic Recording");
283 tabWidget->addTab(createPathTab(), "&File names");
284 tabWidget->addTab(createFormatTab(), "File F&ormat");
285 tabWidget->addTab(createMiscTab(), "&Misc");
286 tabWidget->setUsesScrollButtons(false);
288 QHBoxLayout *hbox = new QHBoxLayout;
289 QPushButton *button = new QPushButton("&Close");
290 button->setDefault(true);
291 connect(button, SIGNAL(clicked(bool)), this, SLOT(accept()));
292 hbox->addStretch();
293 hbox->addWidget(button);
294 vbox->addLayout(hbox);
296 show();
299 void PreferencesDialog::updateFormatSettings() {
300 QVariant v = formatWidget->itemData(formatWidget->currentIndex());
301 // disable
302 if (v != "mp3")
303 for (int i = 0; i < mp3Settings.size(); i++)
304 mp3Settings.at(i)->setEnabled(false);
305 if (v != "vorbis")
306 for (int i = 0; i < vorbisSettings.size(); i++)
307 vorbisSettings.at(i)->setEnabled(false);
308 // enable
309 if (v == "mp3")
310 for (int i = 0; i < mp3Settings.size(); i++)
311 mp3Settings.at(i)->setEnabled(true);
312 if (v == "vorbis")
313 for (int i = 0; i < vorbisSettings.size(); i++)
314 vorbisSettings.at(i)->setEnabled(true);
317 void PreferencesDialog::updateStereoSettings(bool stereo) {
318 for (int i = 0; i < stereoSettings.size(); i++)
319 if (stereo)
320 stereoSettings.at(i)->setEnabled(true);
321 else
322 stereoSettings.at(i)->setEnabled(false);
325 void PreferencesDialog::updateStereoMixLabel(int value) {
326 stereoMixLabel->setText(QString("Stereo mi&x: (left channel: local %1%, remote %2%)").arg(100 - value).arg(value));
329 void PreferencesDialog::editPerCallerPreferences() {
330 perCallerDialog = new PerCallerPreferencesDialog(this);
333 void PreferencesDialog::browseOutputPath() {
334 preferences.get(Pref::OutputPath).set(outputPathEdit->text());
335 QFileDialog dialog(this, "Select output path", getOutputPath());
336 dialog.setFileMode(QFileDialog::DirectoryOnly);
337 if (!dialog.exec())
338 return;
339 QStringList list = dialog.selectedFiles();
340 if (!list.size())
341 return;
342 QString path = list.at(0);
343 QString home = QDir::homePath();
344 if (path.startsWith(home + '/') || path == home)
345 path.replace(0, home.size(), '~');
346 if (path.endsWith('/') || path.endsWith('\\'))
347 path.chop(1);
348 outputPathEdit->setText(path);
351 void PreferencesDialog::updateAbsolutePathWarning(const QString &string) {
352 if (string.startsWith('/') || string.startsWith("~/") || string == "~")
353 absolutePathWarningLabel->hide();
354 else
355 absolutePathWarningLabel->show();
358 void PreferencesDialog::hideEvent(QHideEvent *event) {
359 if (perCallerDialog)
360 perCallerDialog->accept();
362 QDialog::hideEvent(event);
365 void PreferencesDialog::updatePatternToolTip(const QString &pattern) {
366 QString tip =
367 "This pattern specifies how the file name for the recorded call is constructed.\n"
368 "You can use the following directives:\n\n"
370 #define X(a, b) "\t" a "\t" b "\n"
371 X("&s" , "The remote skype name or phone number")
372 X("&d" , "The remote display name")
373 X("&t" , "Your skype name")
374 X("&e" , "Your display name")
375 X("&&" , "Literal & character")
376 X("%Y" , "Year")
377 X("%A / %a", "Full / abbreviated weekday name")
378 X("%B / %b", "Full / abbreviated month name")
379 X("%m" , "Month as a number (01 - 12)")
380 X("%d" , "Day of the month (01 - 31)")
381 X("%H" , "Hour as a 24-hour clock (00 - 23)")
382 X("%I" , "Hour as a 12-hour clock (01 - 12)")
383 X("%p" , "AM or PM")
384 X("%M" , "Minutes (00 - 59)")
385 X("%S" , "Seconds (00 - 59)")
386 X("%%" , "Literal % character")
387 #undef X
388 "\t...and all other directives provided by strftime()\n\n"
390 "With the current choice, the file name might look like this:\n";
392 QString fn = getFileName("echo123", "Skype Test Service", "myskype", "My Full Name",
393 QDateTime::currentDateTime(), pattern);
394 tip += fn;
395 if (fn.contains(':'))
396 tip += "\n\nWARNING: Microsoft Windows does not allow colon characters (:) in file names.";
397 patternWidget->setToolTip(tip);
400 void PreferencesDialog::closePerCallerDialog() {
401 if (perCallerDialog)
402 perCallerDialog->accept();
405 // per caller preferences editor
407 PerCallerPreferencesDialog::PerCallerPreferencesDialog(QWidget *parent) : QDialog(parent) {
408 setWindowTitle("Per Caller Preferences");
409 setWindowModality(Qt::WindowModal);
410 setAttribute(Qt::WA_DeleteOnClose);
412 model = new PerCallerModel(this);
414 QHBoxLayout *bighbox = new QHBoxLayout(this);
415 QVBoxLayout *vbox = new QVBoxLayout;
417 listWidget = new QListView;
418 listWidget->setModel(model);
419 listWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
420 listWidget->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked);
421 connect(listWidget->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(selectionChanged()));
422 vbox->addWidget(listWidget);
424 QVBoxLayout *frame = makeVFrame(vbox, "Preference for selected Skype names:");
425 radioYes = new QRadioButton("Automatically &record calls");
426 radioAsk = new QRadioButton("&Ask every time");
427 radioNo = new QRadioButton("Do &not automatically record calls");
428 connect(radioYes, SIGNAL(clicked(bool)), this, SLOT(radioChanged()));
429 connect(radioAsk, SIGNAL(clicked(bool)), this, SLOT(radioChanged()));
430 connect(radioNo, SIGNAL(clicked(bool)), this, SLOT(radioChanged()));
431 frame->addWidget(radioYes);
432 frame->addWidget(radioAsk);
433 frame->addWidget(radioNo);
435 bighbox->addLayout(vbox);
437 vbox = new QVBoxLayout;
439 QPushButton *button = new QPushButton("A&dd");
440 connect(button, SIGNAL(clicked(bool)), this, SLOT(add()));
441 vbox->addWidget(button);
443 button = new QPushButton("Re&move");
444 connect(button, SIGNAL(clicked(bool)), this, SLOT(remove()));
445 vbox->addWidget(button);
447 vbox->addStretch();
449 button = new QPushButton("&Close");
450 button->setDefault(true);
451 connect(button, SIGNAL(clicked(bool)), this, SLOT(accept()));
452 vbox->addWidget(button);
454 bighbox->addLayout(vbox);
456 // fill in data
458 QSet<QString> seen;
460 QStringList list = preferences.get(Pref::AutoRecordYes).toList();
461 for (int i = 0; i < list.count(); i++) {
462 QString sn = list.at(i);
463 if (seen.contains(sn))
464 continue;
465 seen.insert(sn);
466 add(sn, 2, false);
469 list = preferences.get(Pref::AutoRecordAsk).toList();
470 for (int i = 0; i < list.count(); i++) {
471 QString sn = list.at(i);
472 if (seen.contains(sn))
473 continue;
474 seen.insert(sn);
475 add(sn, 1, false);
478 list = preferences.get(Pref::AutoRecordNo).toList();
479 for (int i = 0; i < list.count(); i++) {
480 QString sn = list.at(i);
481 if (seen.contains(sn))
482 continue;
483 seen.insert(sn);
484 add(sn, 0, false);
487 model->sort();
488 connect(this, SIGNAL(finished(int)), this, SLOT(save()));
489 selectionChanged();
490 show();
493 void PerCallerPreferencesDialog::add(const QString &name, int mode, bool edit) {
494 int i = model->rowCount();
495 model->insertRow(i);
497 QModelIndex idx = model->index(i, 0);
498 model->setData(idx, name, Qt::EditRole);
499 model->setData(idx, mode, Qt::UserRole);
501 if (edit) {
502 listWidget->clearSelection();
503 listWidget->setCurrentIndex(idx);
504 listWidget->edit(idx);
508 void PerCallerPreferencesDialog::remove() {
509 QModelIndexList sel = listWidget->selectionModel()->selectedIndexes();
510 qSort(sel);
511 while (!sel.isEmpty())
512 model->removeRow(sel.takeLast().row());
515 void PerCallerPreferencesDialog::selectionChanged() {
516 QModelIndexList sel = listWidget->selectionModel()->selectedIndexes();
517 bool notEmpty = !sel.isEmpty();
518 int mode = -1;
519 while (!sel.isEmpty()) {
520 int m = model->data(sel.takeLast(), Qt::UserRole).toInt();
521 if (mode == -1) {
522 mode = m;
523 } else if (mode != m) {
524 mode = -1;
525 break;
528 if (mode == -1) {
529 // Qt is a bit annoying about this: You can't deselect
530 // everything unless you disable auto-exclusive mode
531 radioYes->setAutoExclusive(false);
532 radioAsk->setAutoExclusive(false);
533 radioNo ->setAutoExclusive(false);
534 radioYes->setChecked(false);
535 radioAsk->setChecked(false);
536 radioNo ->setChecked(false);
537 radioYes->setAutoExclusive(true);
538 radioAsk->setAutoExclusive(true);
539 radioNo ->setAutoExclusive(true);
540 } else if (mode == 0) {
541 radioNo->setChecked(true);
542 } else if (mode == 1) {
543 radioAsk->setChecked(true);
544 } else if (mode == 2) {
545 radioYes->setChecked(true);
548 radioYes->setEnabled(notEmpty);
549 radioAsk->setEnabled(notEmpty);
550 radioNo ->setEnabled(notEmpty);
553 void PerCallerPreferencesDialog::radioChanged() {
554 int mode = 1;
555 if (radioYes->isChecked())
556 mode = 2;
557 else if (radioNo->isChecked())
558 mode = 0;
560 QModelIndexList sel = listWidget->selectionModel()->selectedIndexes();
561 while (!sel.isEmpty())
562 model->setData(sel.takeLast(), mode, Qt::UserRole);
565 void PerCallerPreferencesDialog::save() {
566 model->sort();
567 int n = model->rowCount();
568 QStringList yes, ask, no;
569 for (int i = 0; i < n; i++) {
570 QModelIndex idx = model->index(i, 0);
571 QString sn = model->data(idx, Qt::EditRole).toString();
572 if (sn.isEmpty())
573 continue;
574 int mode = model->data(idx, Qt::UserRole).toInt();
575 if (mode == 0)
576 no.append(sn);
577 else if (mode == 1)
578 ask.append(sn);
579 else if (mode == 2)
580 yes.append(sn);
582 preferences.get(Pref::AutoRecordYes).set(yes);
583 preferences.get(Pref::AutoRecordAsk).set(ask);
584 preferences.get(Pref::AutoRecordNo).set(no);
587 // per caller model
589 int PerCallerModel::rowCount(const QModelIndex &) const {
590 return skypeNames.count();
593 namespace {
594 const char *PerCallerModel_data_table[3] = {
595 "Don't record", "Ask", "Automatic"
599 QVariant PerCallerModel::data(const QModelIndex &index, int role) const {
600 if (!index.isValid() || index.row() >= skypeNames.size())
601 return QVariant();
602 if (role == Qt::DisplayRole) {
603 int i = index.row();
604 return skypeNames.at(i) + " - " + PerCallerModel_data_table[modes.at(i)];
606 if (role == Qt::EditRole)
607 return skypeNames.at(index.row());
608 if (role == Qt::UserRole)
609 return modes.at(index.row());
610 return QVariant();
613 bool PerCallerModel::setData(const QModelIndex &index, const QVariant &value, int role) {
614 if (!index.isValid() || index.row() >= skypeNames.size())
615 return false;
616 if (role == Qt::EditRole) {
617 skypeNames[index.row()] = value.toString();
618 emit dataChanged(index, index);
619 return true;
621 if (role == Qt::UserRole) {
622 modes[index.row()] = value.toInt();
623 emit dataChanged(index, index);
624 return true;
626 return false;
629 bool PerCallerModel::insertRows(int position, int rows, const QModelIndex &) {
630 beginInsertRows(QModelIndex(), position, position + rows - 1);
631 for (int i = 0; i < rows; i++) {
632 skypeNames.insert(position, "");
633 modes.insert(position, 1);
635 endInsertRows();
636 return true;
639 bool PerCallerModel::removeRows(int position, int rows, const QModelIndex &) {
640 beginRemoveRows(QModelIndex(), position, position + rows - 1);
641 for (int i = 0; i < rows; i++) {
642 skypeNames.removeAt(position);
643 modes.removeAt(position);
645 endRemoveRows();
646 return true;
649 void PerCallerModel::sort(int, Qt::SortOrder) {
650 typedef QPair<QString, int> Pair;
651 typedef QList<Pair> List;
652 List list;
653 for (int i = 0; i < skypeNames.size(); i++)
654 list.append(Pair(skypeNames.at(i), modes.at(i)));
655 qSort(list);
656 for (int i = 0; i < skypeNames.size(); i++) {
657 skypeNames[i] = list.at(i).first;
658 modes[i] = list.at(i).second;
660 reset();
663 Qt::ItemFlags PerCallerModel::flags(const QModelIndex &index) const {
664 Qt::ItemFlags flags = QAbstractListModel::flags(index);
665 if (!index.isValid() || index.row() >= skypeNames.size())
666 return flags;
667 return flags | Qt::ItemIsEditable;
670 // preference
672 void Preference::listAdd(const QString &value) {
673 QStringList list = toList();
674 if (!list.contains(value)) {
675 list.append(value);
676 set(list);
680 void Preference::listRemove(const QString &value) {
681 QStringList list = toList();
682 if (list.removeAll(value))
683 set(list);
686 bool Preference::listContains(const QString &value) {
687 QStringList list = toList();
688 return list.contains(value);
691 // base preferences
693 BasePreferences::~BasePreferences() {
694 clear();
697 bool BasePreferences::load(const QString &filename) {
698 clear();
699 QFile file(filename);
700 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
701 debug(QString("Can't open '%1' for loading preferences").arg(filename));
702 return false;
704 char buf[65536];
705 while (!file.atEnd()) {
706 qint64 len = file.readLine(buf, sizeof(buf));
707 if (len == -1)
708 break;
709 QString line = QString::fromUtf8(buf);
710 line = line.trimmed();
711 if (line.at(0) == '#')
712 continue;
713 int index = line.indexOf('=');
714 if (index < 0)
715 // TODO warn
716 continue;
717 get(line.left(index).trimmed()).set(line.mid(index + 1).trimmed());
719 debug(QString("Loaded %1 preferences from '%2'").arg(prefs.size()).arg(filename));
720 return true;
723 namespace {
724 bool comparePreferencePointers(const Preference *p1, const Preference *p2)
726 return *p1 < *p2;
730 bool BasePreferences::save(const QString &filename) {
731 qSort(prefs.begin(), prefs.end(), comparePreferencePointers);
732 QFile file(filename);
733 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
734 debug(QString("Can't open '%1' for saving preferences").arg(filename));
735 return false;
737 QTextStream out(&file);
738 for (int i = 0; i < prefs.size(); i++) {
739 const Preference &p = *prefs.at(i);
740 out << p.name() << " = " << p.toString() << "\n";
742 debug(QString("Saved %1 preferences to '%2'").arg(prefs.size()).arg(filename));
743 return true;
746 Preference &BasePreferences::get(const QString &name) {
747 for (int i = 0; i < prefs.size(); i++)
748 if (prefs.at(i)->name() == name)
749 return *prefs[i];
750 prefs.append(new Preference(name));
751 return *prefs.last();
754 void BasePreferences::remove(const QString &name) {
755 for (int i = 0; i < prefs.size(); i++) {
756 if (prefs.at(i)->name() == name) {
757 delete prefs.takeAt(i);
758 return;
763 bool BasePreferences::exists(const QString &name) const {
764 for (int i = 0; i < prefs.size(); i++)
765 if (prefs.at(i)->name() == name)
766 return true;
767 return false;
770 void BasePreferences::clear() {
771 for (int i = 0; i < prefs.size(); i++)
772 delete prefs.at(i);
773 prefs.clear();
776 // preferences
778 void Preferences::setPerCallerPreference(const QString &sn, int mode) {
779 // this would interfer with the per caller dialog
780 recorderInstance->closePerCallerDialog();
782 Preference &pYes = get(Pref::AutoRecordYes);
783 Preference &pAsk = get(Pref::AutoRecordAsk);
784 Preference &pNo = get(Pref::AutoRecordNo);
786 pYes.listRemove(sn);
787 pAsk.listRemove(sn);
788 pNo.listRemove(sn);
790 if (mode == 2)
791 pYes.listAdd(sn);
792 else if (mode == 1)
793 pAsk.listAdd(sn);
794 else if (mode == 0)
795 pNo.listAdd(sn);