1 /* -*- c++ -*- */
2 /*
3  * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt
4  *           https://gqrx.dk/
5  *
6  * Copyright 2013 Christian Lindner DL2VCL, Stefano Leucci.
7  *
8  * Gqrx is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3, or (at your option)
11  * any later version.
12  *
13  * Gqrx is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Gqrx; see the file COPYING.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street,
21  * Boston, MA 02110-1301, USA.
22  */
23 #include <Qt>
24 #include <QFile>
25 #include <QStringList>
26 #include <QTextStream>
27 #include <QString>
28 #include <QSet>
29 #include <algorithm>
30 #include <iostream>
31 #include "bookmarks.h"
32 
33 const QColor TagInfo::DefaultColor(Qt::lightGray);
34 const QString TagInfo::strUntagged("Untagged");
35 Bookmarks* Bookmarks::m_pThis = 0;
36 
Bookmarks()37 Bookmarks::Bookmarks()
38 {
39      TagInfo tag(TagInfo::strUntagged);
40      m_TagList.append(tag);
41 }
42 
create()43 void Bookmarks::create()
44 {
45     m_pThis = new Bookmarks;
46 }
47 
Get()48 Bookmarks& Bookmarks::Get()
49 {
50     return *m_pThis;
51 }
52 
setConfigDir(const QString & cfg_dir)53 void Bookmarks::setConfigDir(const QString& cfg_dir)
54 {
55     m_bookmarksFile = cfg_dir + "/bookmarks.csv";
56     std::cout << "BookmarksFile is " << m_bookmarksFile.toStdString() << std::endl;
57 }
58 
add(BookmarkInfo & info)59 void Bookmarks::add(BookmarkInfo &info)
60 {
61     m_BookmarkList.append(info);
62     std::stable_sort(m_BookmarkList.begin(),m_BookmarkList.end());
63     save();
64     emit( BookmarksChanged() );
65 }
66 
remove(int index)67 void Bookmarks::remove(int index)
68 {
69     m_BookmarkList.removeAt(index);
70     save();
71     emit BookmarksChanged();
72 }
73 
load()74 bool Bookmarks::load()
75 {
76     QFile file(m_bookmarksFile);
77     if (file.open(QIODevice::ReadOnly | QIODevice::Text))
78     {
79         m_BookmarkList.clear();
80         m_TagList.clear();
81 
82         // always create the "Untagged" entry.
83         findOrAddTag(TagInfo::strUntagged);
84 
85         // Read Tags, until first empty line.
86         while (!file.atEnd())
87         {
88             QString line = QString::fromUtf8(file.readLine().trimmed());
89 
90             if(line.isEmpty())
91                 break;
92 
93             if(line.startsWith("#"))
94                 continue;
95 
96             QStringList strings = line.split(";");
97             if(strings.count() == 2)
98             {
99                 TagInfo &info = findOrAddTag(strings[0]);
100                 info.color = QColor(strings[1].trimmed());
101             }
102             else
103             {
104                 std::cout << "Bookmarks: Ignoring Line:" << std::endl;
105                 std::cout << "  " << line.toStdString() << std::endl;
106             }
107         }
108         std::sort(m_TagList.begin(),m_TagList.end());
109 
110         // Read Bookmarks, after first empty line.
111         while (!file.atEnd())
112         {
113             QString line = QString::fromUtf8(file.readLine().trimmed());
114             if(line.isEmpty() || line.startsWith("#"))
115                 continue;
116 
117             QStringList strings = line.split(";");
118             if(strings.count() == 5)
119             {
120                 BookmarkInfo info;
121                 info.frequency  = strings[0].toLongLong();
122                 info.name       = strings[1].trimmed();
123                 info.modulation = strings[2].trimmed();
124                 info.bandwidth  = strings[3].toInt();
125                 // Multiple Tags may be separated by comma.
126                 QString strTags = strings[4];
127                 QStringList TagList = strTags.split(",");
128                 for(int iTag=0; iTag<TagList.size(); ++iTag)
129                 {
130                   info.tags.append(&findOrAddTag(TagList[iTag].trimmed()));
131                 }
132 
133                 m_BookmarkList.append(info);
134             }
135             else
136             {
137                 std::cout << "Bookmarks: Ignoring Line:" << std::endl;
138                 std::cout << "  " << line.toStdString() << std::endl;
139             }
140         }
141         file.close();
142         std::stable_sort(m_BookmarkList.begin(),m_BookmarkList.end());
143 
144         emit BookmarksChanged();
145         return true;
146     }
147     return false;
148 }
149 
150 //FIXME: Commas in names
save()151 bool Bookmarks::save()
152 {
153     QFile file(m_bookmarksFile);
154     if(file.open(QFile::WriteOnly | QFile::Truncate | QIODevice::Text))
155     {
156         QTextStream stream(&file);
157 
158         stream << QString("# Tag name").leftJustified(20) + "; " +
159                   QString(" color") << endl;
160 
161         QSet<TagInfo*> usedTags;
162         for (int iBookmark = 0; iBookmark < m_BookmarkList.size(); iBookmark++)
163         {
164             BookmarkInfo& info = m_BookmarkList[iBookmark];
165             for(int iTag = 0; iTag < info.tags.size(); ++iTag)
166             {
167               TagInfo& tag = *info.tags[iTag];
168               usedTags.insert(&tag);
169             }
170         }
171 
172         for (QSet<TagInfo*>::iterator i = usedTags.begin(); i != usedTags.end(); i++)
173         {
174             TagInfo& info = **i;
175             stream << info.name.leftJustified(20) + "; " + info.color.name() << endl;
176         }
177 
178         stream << endl;
179 
180         stream << QString("# Frequency").leftJustified(12) + "; " +
181                   QString("Name").leftJustified(25)+ "; " +
182                   QString("Modulation").leftJustified(20) + "; " +
183                   QString("Bandwidth").rightJustified(10) + "; " +
184                   QString("Tags") << endl;
185 
186         for (int i = 0; i < m_BookmarkList.size(); i++)
187         {
188             BookmarkInfo& info = m_BookmarkList[i];
189             QString line = QString::number(info.frequency).rightJustified(12) +
190                     "; " + info.name.leftJustified(25) + "; " +
191                     info.modulation.leftJustified(20)+ "; " +
192                     QString::number(info.bandwidth).rightJustified(10) + "; ";
193             for(int iTag = 0; iTag<info.tags.size(); ++iTag)
194             {
195                 TagInfo& tag = *info.tags[iTag];
196                 if(iTag!=0)
197                 {
198                     line.append(",");
199                 }
200                 line.append(tag.name);
201             }
202 
203             stream << line << endl;
204         }
205 
206         file.close();
207         return true;
208     }
209     return false;
210 }
211 
getBookmarksInRange(qint64 low,qint64 high)212 QList<BookmarkInfo> Bookmarks::getBookmarksInRange(qint64 low, qint64 high)
213 {
214     BookmarkInfo info;
215     info.frequency=low;
216     QList<BookmarkInfo>::const_iterator lb = std::lower_bound(m_BookmarkList.begin(), m_BookmarkList.end(), info);
217     info.frequency=high;
218     QList<BookmarkInfo>::const_iterator ub = std::upper_bound(m_BookmarkList.begin(), m_BookmarkList.end(), info);
219 
220     QList<BookmarkInfo> found;
221 
222     while (lb != ub)
223     {
224         const BookmarkInfo& info = *lb;
225         //if(info.IsActive())
226         {
227           found.append(info);
228         }
229         lb++;
230     }
231 
232     return found;
233 
234 }
235 
findOrAddTag(QString tagName)236 TagInfo &Bookmarks::findOrAddTag(QString tagName)
237 {
238     tagName = tagName.trimmed();
239 
240     if (tagName.isEmpty())
241         tagName=TagInfo::strUntagged;
242 
243     int idx = getTagIndex(tagName);
244 
245     if (idx != -1)
246         return m_TagList[idx];
247 
248     TagInfo info;
249     info.name=tagName;
250     m_TagList.append(info);
251     emit TagListChanged();
252     return m_TagList.last();
253 }
254 
removeTag(QString tagName)255 bool Bookmarks::removeTag(QString tagName)
256 {
257     tagName = tagName.trimmed();
258 
259     // Do not delete "Untagged" tag.
260     if(tagName.compare(TagInfo::strUntagged, tagName)==0)
261         return false;
262 
263     int idx = getTagIndex(tagName);
264     if (idx == -1)
265         return false;
266 
267     // Delete Tag from all Bookmarks that use it.
268     TagInfo* pTagToDelete = &m_TagList[idx];
269     for(int i=0; i<m_BookmarkList.size(); ++i)
270     {
271         BookmarkInfo& bmi = m_BookmarkList[i];
272         for(int t=0; t<bmi.tags.size(); ++t)
273         {
274             TagInfo* pTag = bmi.tags[t];
275             if(pTag == pTagToDelete)
276             {
277                 if(bmi.tags.size()>1) bmi.tags.removeAt(t);
278                 else bmi.tags[0] = &findOrAddTag(TagInfo::strUntagged);
279             }
280         }
281     }
282 
283     // Delete Tag.
284     m_TagList.removeAt(idx);
285 
286     emit BookmarksChanged();
287     emit TagListChanged();
288 
289     return true;
290 }
291 
setTagChecked(QString tagName,bool bChecked)292 bool Bookmarks::setTagChecked(QString tagName, bool bChecked)
293 {
294     int idx = getTagIndex(tagName);
295     if (idx == -1) return false;
296     m_TagList[idx].active = bChecked;
297     emit BookmarksChanged();
298     emit TagListChanged();
299     return true;
300 }
301 
getTagIndex(QString tagName)302 int Bookmarks::getTagIndex(QString tagName)
303 {
304     tagName = tagName.trimmed();
305     for (int i = 0; i < m_TagList.size(); i++)
306     {
307         if (m_TagList[i].name == tagName)
308             return i;
309     }
310 
311     return -1;
312 }
313 
GetColor() const314 const QColor BookmarkInfo::GetColor() const
315 {
316     for(int iTag=0; iTag<tags.size(); ++iTag)
317     {
318         TagInfo& tag = *tags[iTag];
319         if(tag.active)
320         {
321             return tag.color;
322         }
323     }
324     return TagInfo::DefaultColor;
325 }
326 
IsActive() const327 bool BookmarkInfo::IsActive() const
328 {
329     bool bActive = false;
330     for(int iTag=0; iTag<tags.size(); ++iTag)
331     {
332         TagInfo& tag = *tags[iTag];
333         if(tag.active)
334         {
335             bActive = true;
336             break;
337         }
338     }
339     return bActive;
340 }
341