Fix creation of output file name
[skype-call-recorder.git] / preferences.cpp
blob47cbe9afc46af1158d0e5aee58fe93db0f32b24c
1 /*
2 Skype Call Recorder
3 Copyright 2008 - 2009 by 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 &pat)
72 QString pattern = pat.isEmpty() ? preferences.get(Pref::OutputPattern).toString() : pat;
73 QString fileName;
75 for (int i = 0; i < pattern.size(); i++) {
76 if (pattern.at(i) == QChar('&') && i + 1 < pattern.size()) {
77 i++;
78 if (pattern.at(i) == QChar('s'))
79 fileName += escape(skypeName);
80 else if (pattern.at(i) == QChar('d'))
81 fileName += escape(displayName);
82 else if (pattern.at(i) == QChar('t'))
83 fileName += escape(mySkypeName);
84 else if (pattern.at(i) == QChar('e'))
85 fileName += escape(myDisplayName);
86 else if (pattern.at(i) == QChar('&'))
87 fileName += QChar('&');
88 else {
89 fileName += QChar('&');
90 fileName += pattern.at(i);
92 } else {
93 fileName += pattern.at(i);
97 // TODO: uhm, does QT provide any time formatting the strftime() way?
98 char *buf = new char[fileName.size() + 1024];
99 time_t t = timestamp.toTime_t();
100 struct tm *tm = std::localtime(&t);
101 std::strftime(buf, fileName.size() + 1024, fileName.toUtf8().constData(), tm);
102 fileName = QString::fromLocal8Bit(buf);
103 delete[] buf;
105 return getOutputPath() + '/' + fileName;
108 // preferences dialog
110 static QVBoxLayout *makeVFrame(QVBoxLayout *parentLayout, const char *title) {
111 QGroupBox *box = new QGroupBox(title);
112 QVBoxLayout *vbox = new QVBoxLayout(box);
113 parentLayout->addWidget(box);
114 return vbox;
117 QWidget *PreferencesDialog::createRecordingTab() {
118 QWidget *widget = new QWidget;
119 QVBoxLayout *vbox = new QVBoxLayout(widget);
121 Preference &preference = preferences.get(Pref::AutoRecordDefault);
122 SmartRadioButton *radio = new SmartRadioButton("Automatically &record all calls", preference, "yes");
123 vbox->addWidget(radio);
124 radio = new SmartRadioButton("&Ask for every call", preference, "ask");
125 vbox->addWidget(radio);
126 radio = new SmartRadioButton("Do &not automatically record calls", preference, "no");
127 vbox->addWidget(radio);
129 QPushButton *button = new QPushButton("Edit &per caller preferences");
130 connect(button, SIGNAL(clicked(bool)), this, SLOT(editPerCallerPreferences()));
131 vbox->addWidget(button);
133 SmartCheckBox *check = new SmartCheckBox("Show &balloon notification when recording starts", preferences.get(Pref::NotifyRecordingStart));
134 vbox->addWidget(check);
136 vbox->addStretch();
137 return widget;
140 QWidget *PreferencesDialog::createPathTab() {
141 QWidget *widget = new QWidget;
142 QVBoxLayout *vbox = new QVBoxLayout(widget);
144 QLabel *label = new QLabel("&Save recorded calls here:");
145 outputPathEdit = new SmartLineEdit(preferences.get(Pref::OutputPath));
146 label->setBuddy(outputPathEdit);
147 connect(outputPathEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateAbsolutePathWarning(const QString &)));
148 QPushButton *button = new QPushButton(QFileIconProvider().icon(QFileIconProvider::Folder), "Browse");
149 connect(button, SIGNAL(clicked(bool)), this, SLOT(browseOutputPath()));
150 QHBoxLayout *hbox = new QHBoxLayout;
151 hbox->addWidget(outputPathEdit);
152 hbox->addWidget(button);
153 vbox->addWidget(label);
154 vbox->addLayout(hbox);
156 label = new QLabel("File &name:");
157 patternWidget = new SmartEditableComboBox(preferences.get(Pref::OutputPattern));
158 label->setBuddy(patternWidget);
159 patternWidget->addItem("%Y-%m-%d %H:%M:%S Call with &s");
160 patternWidget->addItem("Call with &s, %a %b %d %Y, %H:%M:%S");
161 patternWidget->addItem("%Y, %B/Call with &s, %a %b %d %Y, %H:%M:%S");
162 patternWidget->addItem("Calls with &s/Call with &s, %a %b %d %Y, %H:%M:%S");
163 patternWidget->setupDone();
164 connect(patternWidget, SIGNAL(editTextChanged(const QString &)), this, SLOT(updatePatternToolTip(const QString &)));
165 vbox->addWidget(label);
166 vbox->addWidget(patternWidget);
168 vbox->addStretch();
170 absolutePathWarningLabel = new QLabel("<b>Warning:</b> The path you have entered is not an absolute path!");
171 vbox->addWidget(absolutePathWarningLabel);
173 updatePatternToolTip("");
174 updateAbsolutePathWarning(preferences.get(Pref::OutputPath).toString());
176 return widget;
179 QWidget *PreferencesDialog::createFormatTab() {
180 QWidget *widget = new QWidget;
181 QVBoxLayout *vbox = new QVBoxLayout(widget);
182 QGridLayout *grid = new QGridLayout;
184 QLabel *label = new QLabel("Fil&e format:");
185 formatWidget = new SmartComboBox(preferences.get(Pref::OutputFormat));
186 label->setBuddy(formatWidget);
187 formatWidget->addItem("WAV PCM", "wav");
188 formatWidget->addItem("MP3", "mp3");
189 formatWidget->addItem("Ogg Vorbis", "vorbis");
190 formatWidget->setupDone();
191 connect(formatWidget, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFormatSettings()));
192 grid->addWidget(label, 0, 0);
193 grid->addWidget(formatWidget, 0, 1);
195 label = new QLabel("MP3 &bitrate:");
196 SmartComboBox *combo = new SmartComboBox(preferences.get(Pref::OutputFormatMp3Bitrate));
197 label->setBuddy(combo);
198 combo->addItem("8 kbps", 8);
199 combo->addItem("16 kbps", 16);
200 combo->addItem("24 kbps", 24);
201 combo->addItem("32 kbps (recommended for mono)", 32);
202 combo->addItem("40 kbps", 40);
203 combo->addItem("48 kbps", 48);
204 combo->addItem("56 kbps", 56);
205 combo->addItem("64 kbps (recommended for stereo)", 64);
206 combo->addItem("80 kbps", 80);
207 combo->addItem("96 kbps", 96);
208 combo->addItem("112 kbps", 112);
209 combo->addItem("128 kbps", 128);
210 combo->addItem("144 kbps", 144);
211 combo->addItem("160 kbps", 160);
212 combo->setupDone();
213 mp3Settings.append(label);
214 mp3Settings.append(combo);
215 grid->addWidget(label, 1, 0);
216 grid->addWidget(combo, 1, 1);
218 label = new QLabel("Ogg Vorbis &quality:");
219 combo = new SmartComboBox(preferences.get(Pref::OutputFormatVorbisQuality));
220 label->setBuddy(combo);
221 combo->addItem("Quality -1", -1);
222 combo->addItem("Quality 0", 0);
223 combo->addItem("Quality 1", 1);
224 combo->addItem("Quality 2", 2);
225 combo->addItem("Quality 3 (recommended)", 3);
226 combo->addItem("Quality 4", 4);
227 combo->addItem("Quality 5", 5);
228 combo->addItem("Quality 6", 6);
229 combo->addItem("Quality 7", 7);
230 combo->addItem("Quality 8", 8);
231 combo->addItem("Quality 9", 9);
232 combo->addItem("Quality 10", 10);
233 combo->setupDone();
234 vorbisSettings.append(label);
235 vorbisSettings.append(combo);
236 grid->addWidget(label, 2, 0);
237 grid->addWidget(combo, 2, 1);
239 vbox->addLayout(grid);
241 SmartCheckBox *check = new SmartCheckBox("Save to &stereo file", preferences.get(Pref::OutputStereo));
242 connect(check, SIGNAL(clicked(bool)), this, SLOT(updateStereoSettings(bool)));
243 vbox->addWidget(check);
245 stereoMixLabel = new QLabel("");
246 SmartSlider *slider = new SmartSlider(preferences.get(Pref::OutputStereoMix));
247 stereoMixLabel->setBuddy(slider);
248 slider->setOrientation(Qt::Horizontal);
249 slider->setRange(0, 100);
250 slider->setSingleStep(1);
251 slider->setPageStep(10);
252 slider->setTickPosition(QSlider::TicksBelow);
253 slider->setTickInterval(10);
254 slider->setupDone();
255 connect(slider, SIGNAL(valueChanged(int)), this, SLOT(updateStereoMixLabel(int)));
256 stereoSettings.append(stereoMixLabel);
257 stereoSettings.append(slider);
258 vbox->addWidget(stereoMixLabel);
259 vbox->addWidget(slider);
261 check = new SmartCheckBox("Save call &information in files", preferences.get(Pref::OutputSaveTags));
262 mp3Settings.append(check);
263 vorbisSettings.append(check);
264 vbox->addWidget(check);
266 vbox->addStretch();
267 updateFormatSettings();
268 updateStereoSettings(preferences.get(Pref::OutputStereo).toBool());
269 updateStereoMixLabel(preferences.get(Pref::OutputStereoMix).toInt());
270 return widget;
273 QWidget *PreferencesDialog::createMiscTab() {
274 QWidget *widget = new QWidget;
275 QVBoxLayout *vbox = new QVBoxLayout(widget);
277 SmartCheckBox *check = new SmartCheckBox("&Display a small main window. Enable this if your\n"
278 "environment does not provide a system tray (needs restart)", preferences.get(Pref::GuiWindowed));
279 vbox->addWidget(check);
281 vbox->addStretch();
282 return widget;
285 PreferencesDialog::PreferencesDialog() {
286 setWindowTitle(PROGRAM_NAME " - Preferences");
287 setAttribute(Qt::WA_DeleteOnClose);
289 QVBoxLayout *vbox = new QVBoxLayout(this);
290 vbox->setSizeConstraint(QLayout::SetFixedSize);
292 QTabWidget *tabWidget = new QTabWidget;
293 vbox->addWidget(tabWidget);
295 tabWidget->addTab(createRecordingTab(), "Au&tomatic Recording");
296 tabWidget->addTab(createPathTab(), "&File names");
297 tabWidget->addTab(createFormatTab(), "File F&ormat");
298 tabWidget->addTab(createMiscTab(), "&Misc");
299 tabWidget->setUsesScrollButtons(false);
301 QHBoxLayout *hbox = new QHBoxLayout;
302 QPushButton *button = new QPushButton("&Close");
303 button->setDefault(true);
304 connect(button, SIGNAL(clicked(bool)), this, SLOT(accept()));
305 hbox->addStretch();
306 hbox->addWidget(button);
307 vbox->addLayout(hbox);
309 show();
312 void PreferencesDialog::updateFormatSettings() {
313 QVariant v = formatWidget->itemData(formatWidget->currentIndex());
314 // disable
315 if (v != "mp3")
316 for (int i = 0; i < mp3Settings.size(); i++)
317 mp3Settings.at(i)->setEnabled(false);
318 if (v != "vorbis")
319 for (int i = 0; i < vorbisSettings.size(); i++)
320 vorbisSettings.at(i)->setEnabled(false);
321 // enable
322 if (v == "mp3")
323 for (int i = 0; i < mp3Settings.size(); i++)
324 mp3Settings.at(i)->setEnabled(true);
325 if (v == "vorbis")
326 for (int i = 0; i < vorbisSettings.size(); i++)
327 vorbisSettings.at(i)->setEnabled(true);
330 void PreferencesDialog::updateStereoSettings(bool stereo) {
331 for (int i = 0; i < stereoSettings.size(); i++)
332 if (stereo)
333 stereoSettings.at(i)->setEnabled(true);
334 else
335 stereoSettings.at(i)->setEnabled(false);
338 void PreferencesDialog::updateStereoMixLabel(int value) {
339 stereoMixLabel->setText(QString("Stereo mi&x: (left channel: local %1%, remote %2%)").arg(100 - value).arg(value));
342 void PreferencesDialog::editPerCallerPreferences() {
343 perCallerDialog = new PerCallerPreferencesDialog(this);
346 void PreferencesDialog::browseOutputPath() {
347 preferences.get(Pref::OutputPath).set(outputPathEdit->text());
348 QFileDialog dialog(this, "Select output path", getOutputPath());
349 dialog.setFileMode(QFileDialog::DirectoryOnly);
350 if (!dialog.exec())
351 return;
352 QStringList list = dialog.selectedFiles();
353 if (!list.size())
354 return;
355 QString path = list.at(0);
356 QString home = QDir::homePath();
357 if (path.startsWith(home + '/') || path == home)
358 path.replace(0, home.size(), '~');
359 if (path.endsWith('/') || path.endsWith('\\'))
360 path.chop(1);
361 outputPathEdit->setText(path);
364 void PreferencesDialog::updateAbsolutePathWarning(const QString &string) {
365 if (string.startsWith('/') || string.startsWith("~/") || string == "~")
366 absolutePathWarningLabel->hide();
367 else
368 absolutePathWarningLabel->show();
371 void PreferencesDialog::hideEvent(QHideEvent *event) {
372 if (perCallerDialog)
373 perCallerDialog->accept();
375 QDialog::hideEvent(event);
378 void PreferencesDialog::updatePatternToolTip(const QString &pattern) {
379 QString tip =
380 "This pattern specifies how the file name for the recorded call is constructed.\n"
381 "You can use the following directives:\n\n"
383 #define X(a, b) "\t" a "\t" b "\n"
384 X("&s" , "The remote skype name or phone number")
385 X("&d" , "The remote display name")
386 X("&t" , "Your skype name")
387 X("&e" , "Your display name")
388 X("&&" , "Literal & character")
389 X("%Y" , "Year")
390 X("%A / %a", "Full / abbreviated weekday name")
391 X("%B / %b", "Full / abbreviated month name")
392 X("%m" , "Month as a number (01 - 12)")
393 X("%d" , "Day of the month (01 - 31)")
394 X("%H" , "Hour as a 24-hour clock (00 - 23)")
395 X("%I" , "Hour as a 12-hour clock (01 - 12)")
396 X("%p" , "AM or PM")
397 X("%M" , "Minutes (00 - 59)")
398 X("%S" , "Seconds (00 - 59)")
399 X("%%" , "Literal % character")
400 #undef X
401 "\t...and all other directives provided by strftime()\n\n"
403 "With the current choice, the file name might look like this:\n";
405 QString fn = getFileName("echo123", "Skype Test Service", "myskype", "My Full Name",
406 QDateTime::currentDateTime(), pattern);
407 tip += fn;
408 if (fn.contains(':'))
409 tip += "\n\nWARNING: Microsoft Windows does not allow colon characters (:) in file names.";
410 patternWidget->setToolTip(tip);
413 void PreferencesDialog::closePerCallerDialog() {
414 if (perCallerDialog)
415 perCallerDialog->accept();
418 // per caller preferences editor
420 PerCallerPreferencesDialog::PerCallerPreferencesDialog(QWidget *parent) : QDialog(parent) {
421 setWindowTitle("Per Caller Preferences");
422 setWindowModality(Qt::WindowModal);
423 setAttribute(Qt::WA_DeleteOnClose);
425 model = new PerCallerModel(this);
427 QHBoxLayout *bighbox = new QHBoxLayout(this);
428 QVBoxLayout *vbox = new QVBoxLayout;
430 listWidget = new QListView;
431 listWidget->setModel(model);
432 listWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
433 listWidget->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked);
434 connect(listWidget->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(selectionChanged()));
435 vbox->addWidget(listWidget);
437 QVBoxLayout *frame = makeVFrame(vbox, "Preference for selected Skype names:");
438 radioYes = new QRadioButton("Automatically &record calls");
439 radioAsk = new QRadioButton("&Ask every time");
440 radioNo = new QRadioButton("Do &not automatically record calls");
441 connect(radioYes, SIGNAL(clicked(bool)), this, SLOT(radioChanged()));
442 connect(radioAsk, SIGNAL(clicked(bool)), this, SLOT(radioChanged()));
443 connect(radioNo, SIGNAL(clicked(bool)), this, SLOT(radioChanged()));
444 frame->addWidget(radioYes);
445 frame->addWidget(radioAsk);
446 frame->addWidget(radioNo);
448 bighbox->addLayout(vbox);
450 vbox = new QVBoxLayout;
452 QPushButton *button = new QPushButton("A&dd");
453 connect(button, SIGNAL(clicked(bool)), this, SLOT(add()));
454 vbox->addWidget(button);
456 button = new QPushButton("Re&move");
457 connect(button, SIGNAL(clicked(bool)), this, SLOT(remove()));
458 vbox->addWidget(button);
460 vbox->addStretch();
462 button = new QPushButton("&Close");
463 button->setDefault(true);
464 connect(button, SIGNAL(clicked(bool)), this, SLOT(accept()));
465 vbox->addWidget(button);
467 bighbox->addLayout(vbox);
469 // fill in data
471 QSet<QString> seen;
473 QStringList list = preferences.get(Pref::AutoRecordYes).toList();
474 for (int i = 0; i < list.count(); i++) {
475 QString sn = list.at(i);
476 if (seen.contains(sn))
477 continue;
478 seen.insert(sn);
479 add(sn, 2, false);
482 list = preferences.get(Pref::AutoRecordAsk).toList();
483 for (int i = 0; i < list.count(); i++) {
484 QString sn = list.at(i);
485 if (seen.contains(sn))
486 continue;
487 seen.insert(sn);
488 add(sn, 1, false);
491 list = preferences.get(Pref::AutoRecordNo).toList();
492 for (int i = 0; i < list.count(); i++) {
493 QString sn = list.at(i);
494 if (seen.contains(sn))
495 continue;
496 seen.insert(sn);
497 add(sn, 0, false);
500 model->sort();
501 connect(this, SIGNAL(finished(int)), this, SLOT(save()));
502 selectionChanged();
503 show();
506 void PerCallerPreferencesDialog::add(const QString &name, int mode, bool edit) {
507 int i = model->rowCount();
508 model->insertRow(i);
510 QModelIndex idx = model->index(i, 0);
511 model->setData(idx, name, Qt::EditRole);
512 model->setData(idx, mode, Qt::UserRole);
514 if (edit) {
515 listWidget->clearSelection();
516 listWidget->setCurrentIndex(idx);
517 listWidget->edit(idx);
521 void PerCallerPreferencesDialog::remove() {
522 QModelIndexList sel = listWidget->selectionModel()->selectedIndexes();
523 qSort(sel);
524 while (!sel.isEmpty())
525 model->removeRow(sel.takeLast().row());
528 void PerCallerPreferencesDialog::selectionChanged() {
529 QModelIndexList sel = listWidget->selectionModel()->selectedIndexes();
530 bool notEmpty = !sel.isEmpty();
531 int mode = -1;
532 while (!sel.isEmpty()) {
533 int m = model->data(sel.takeLast(), Qt::UserRole).toInt();
534 if (mode == -1) {
535 mode = m;
536 } else if (mode != m) {
537 mode = -1;
538 break;
541 if (mode == -1) {
542 // Qt is a bit annoying about this: You can't deselect
543 // everything unless you disable auto-exclusive mode
544 radioYes->setAutoExclusive(false);
545 radioAsk->setAutoExclusive(false);
546 radioNo ->setAutoExclusive(false);
547 radioYes->setChecked(false);
548 radioAsk->setChecked(false);
549 radioNo ->setChecked(false);
550 radioYes->setAutoExclusive(true);
551 radioAsk->setAutoExclusive(true);
552 radioNo ->setAutoExclusive(true);
553 } else if (mode == 0) {
554 radioNo->setChecked(true);
555 } else if (mode == 1) {
556 radioAsk->setChecked(true);
557 } else if (mode == 2) {
558 radioYes->setChecked(true);
561 radioYes->setEnabled(notEmpty);
562 radioAsk->setEnabled(notEmpty);
563 radioNo ->setEnabled(notEmpty);
566 void PerCallerPreferencesDialog::radioChanged() {
567 int mode = 1;
568 if (radioYes->isChecked())
569 mode = 2;
570 else if (radioNo->isChecked())
571 mode = 0;
573 QModelIndexList sel = listWidget->selectionModel()->selectedIndexes();
574 while (!sel.isEmpty())
575 model->setData(sel.takeLast(), mode, Qt::UserRole);
578 void PerCallerPreferencesDialog::save() {
579 model->sort();
580 int n = model->rowCount();
581 QStringList yes, ask, no;
582 for (int i = 0; i < n; i++) {
583 QModelIndex idx = model->index(i, 0);
584 QString sn = model->data(idx, Qt::EditRole).toString();
585 if (sn.isEmpty())
586 continue;
587 int mode = model->data(idx, Qt::UserRole).toInt();
588 if (mode == 0)
589 no.append(sn);
590 else if (mode == 1)
591 ask.append(sn);
592 else if (mode == 2)
593 yes.append(sn);
595 preferences.get(Pref::AutoRecordYes).set(yes);
596 preferences.get(Pref::AutoRecordAsk).set(ask);
597 preferences.get(Pref::AutoRecordNo).set(no);
600 // per caller model
602 int PerCallerModel::rowCount(const QModelIndex &) const {
603 return skypeNames.count();
606 namespace {
607 const char *PerCallerModel_data_table[3] = {
608 "Don't record", "Ask", "Automatic"
612 QVariant PerCallerModel::data(const QModelIndex &index, int role) const {
613 if (!index.isValid() || index.row() >= skypeNames.size())
614 return QVariant();
615 if (role == Qt::DisplayRole) {
616 int i = index.row();
617 return skypeNames.at(i) + " - " + PerCallerModel_data_table[modes.at(i)];
619 if (role == Qt::EditRole)
620 return skypeNames.at(index.row());
621 if (role == Qt::UserRole)
622 return modes.at(index.row());
623 return QVariant();
626 bool PerCallerModel::setData(const QModelIndex &index, const QVariant &value, int role) {
627 if (!index.isValid() || index.row() >= skypeNames.size())
628 return false;
629 if (role == Qt::EditRole) {
630 skypeNames[index.row()] = value.toString();
631 emit dataChanged(index, index);
632 return true;
634 if (role == Qt::UserRole) {
635 modes[index.row()] = value.toInt();
636 emit dataChanged(index, index);
637 return true;
639 return false;
642 bool PerCallerModel::insertRows(int position, int rows, const QModelIndex &) {
643 beginInsertRows(QModelIndex(), position, position + rows - 1);
644 for (int i = 0; i < rows; i++) {
645 skypeNames.insert(position, "");
646 modes.insert(position, 1);
648 endInsertRows();
649 return true;
652 bool PerCallerModel::removeRows(int position, int rows, const QModelIndex &) {
653 beginRemoveRows(QModelIndex(), position, position + rows - 1);
654 for (int i = 0; i < rows; i++) {
655 skypeNames.removeAt(position);
656 modes.removeAt(position);
658 endRemoveRows();
659 return true;
662 void PerCallerModel::sort(int, Qt::SortOrder) {
663 typedef QPair<QString, int> Pair;
664 typedef QList<Pair> List;
665 List list;
666 for (int i = 0; i < skypeNames.size(); i++)
667 list.append(Pair(skypeNames.at(i), modes.at(i)));
668 qSort(list);
669 for (int i = 0; i < skypeNames.size(); i++) {
670 skypeNames[i] = list.at(i).first;
671 modes[i] = list.at(i).second;
673 reset();
676 Qt::ItemFlags PerCallerModel::flags(const QModelIndex &index) const {
677 Qt::ItemFlags flags = QAbstractListModel::flags(index);
678 if (!index.isValid() || index.row() >= skypeNames.size())
679 return flags;
680 return flags | Qt::ItemIsEditable;
683 // preference
685 void Preference::listAdd(const QString &value) {
686 QStringList list = toList();
687 if (!list.contains(value)) {
688 list.append(value);
689 set(list);
693 void Preference::listRemove(const QString &value) {
694 QStringList list = toList();
695 if (list.removeAll(value))
696 set(list);
699 bool Preference::listContains(const QString &value) {
700 QStringList list = toList();
701 return list.contains(value);
704 // base preferences
706 BasePreferences::~BasePreferences() {
707 clear();
710 bool BasePreferences::load(const QString &filename) {
711 clear();
712 QFile file(filename);
713 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
714 debug(QString("Can't open '%1' for loading preferences").arg(filename));
715 return false;
717 char buf[65536];
718 while (!file.atEnd()) {
719 qint64 len = file.readLine(buf, sizeof(buf));
720 if (len == -1)
721 break;
722 QString line = QString::fromUtf8(buf);
723 line = line.trimmed();
724 if (line.at(0) == '#')
725 continue;
726 int index = line.indexOf('=');
727 if (index < 0)
728 // TODO warn
729 continue;
730 get(line.left(index).trimmed()).set(line.mid(index + 1).trimmed());
732 debug(QString("Loaded %1 preferences from '%2'").arg(prefs.size()).arg(filename));
733 return true;
736 namespace {
737 bool comparePreferencePointers(const Preference *p1, const Preference *p2)
739 return *p1 < *p2;
743 bool BasePreferences::save(const QString &filename) {
744 qSort(prefs.begin(), prefs.end(), comparePreferencePointers);
745 QFile file(filename);
746 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
747 debug(QString("Can't open '%1' for saving preferences").arg(filename));
748 return false;
750 QTextStream out(&file);
751 for (int i = 0; i < prefs.size(); i++) {
752 const Preference &p = *prefs.at(i);
753 out << p.name() << " = " << p.toString() << "\n";
755 debug(QString("Saved %1 preferences to '%2'").arg(prefs.size()).arg(filename));
756 return true;
759 Preference &BasePreferences::get(const QString &name) {
760 for (int i = 0; i < prefs.size(); i++)
761 if (prefs.at(i)->name() == name)
762 return *prefs[i];
763 prefs.append(new Preference(name));
764 return *prefs.last();
767 void BasePreferences::remove(const QString &name) {
768 for (int i = 0; i < prefs.size(); i++) {
769 if (prefs.at(i)->name() == name) {
770 delete prefs.takeAt(i);
771 return;
776 bool BasePreferences::exists(const QString &name) const {
777 for (int i = 0; i < prefs.size(); i++)
778 if (prefs.at(i)->name() == name)
779 return true;
780 return false;
783 void BasePreferences::clear() {
784 for (int i = 0; i < prefs.size(); i++)
785 delete prefs.at(i);
786 prefs.clear();
789 // preferences
791 void Preferences::setPerCallerPreference(const QString &sn, int mode) {
792 // this would interfer with the per caller dialog
793 recorderInstance->closePerCallerDialog();
795 Preference &pYes = get(Pref::AutoRecordYes);
796 Preference &pAsk = get(Pref::AutoRecordAsk);
797 Preference &pNo = get(Pref::AutoRecordNo);
799 pYes.listRemove(sn);
800 pAsk.listRemove(sn);
801 pNo.listRemove(sn);
803 if (mode == 2)
804 pYes.listAdd(sn);
805 else if (mode == 1)
806 pAsk.listAdd(sn);
807 else if (mode == 0)
808 pNo.listAdd(sn);