~ubuntu-branches/ubuntu/saucy/digikam/saucy

« back to all changes in this revision

Viewing changes to extra/kipi-plugins/gpssync/searchwidget.cpp

  • Committer: Package Import Robot
  • Author(s): Felix Geyer, Rohan Garg, Philip Muškovac, Felix Geyer
  • Date: 2011-09-23 18:18:55 UTC
  • mfrom: (1.2.36 upstream)
  • Revision ID: package-import@ubuntu.com-20110923181855-ifs67wxkugshev9k
Tags: 2:2.1.1-0ubuntu1
[ Rohan Garg ]
* New upstream release (LP: #834190)
  - debian/control
    + Build with libqtwebkit-dev
 - debian/kipi-plugins-common
    + Install libkvkontakte required by kipi-plugins
 - debian/digikam
    + Install panoramagui

[ Philip Muškovac ]
* New upstream release
  - debian/control:
    + Add libcv-dev, libcvaux-dev, libhighgui-dev, libboost-graph1.46-dev,
      libksane-dev, libxml2-dev, libxslt-dev, libqt4-opengl-dev, libqjson-dev,
      libgpod-dev and libqca2-dev to build-deps
    + Add packages for kipi-plugins, libmediawiki, libkface, libkgeomap and
      libkvkontakte
  - debian/rules:
    + Don't build with gphoto2 since it doesn't build with it.
  - Add kubuntu_fix_test_linking.diff to fix linking of the dngconverter test
  - update install files
  - update kubuntu_01_mysqld_executable_name.diff for new cmake layout
    and rename to kubuntu_mysqld_executable_name.diff
* Fix typo in digikam-data description (LP: #804894)
* Fix Vcs links

[ Felix Geyer ]
* Move library data files to the new packages libkface-data, libkgeomap-data
  and libkvkontakte-data.
* Override version of the embedded library packages to 1.0~digikam<version>.
* Exclude the library packages from digikam-dbg to prevent file conflicts in
  the future.
* Call dh_install with --list-missing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/** ===========================================================
 
2
 * @file
 
3
 *
 
4
 * This file is a part of kipi-plugins project
 
5
 * <a href="http://www.kipi-plugins.org">http://www.kipi-plugins.org</a>
 
6
 *
 
7
 * @date   2010-06-01
 
8
 * @brief  A widget to search for places.
 
9
 *
 
10
 * @author Copyright (C) 2010, 2011 by Michael G. Hansen
 
11
 *         <a href="mailto:mike at mghansen dot de">mike at mghansen dot de</a>
 
12
 *
 
13
 * This program is free software; you can redistribute it
 
14
 * and/or modify it under the terms of the GNU General
 
15
 * Public License as published by the Free Software Foundation;
 
16
 * either version 2, or (at your option) any later version.
 
17
 *
 
18
 * This program is distributed in the hope that it will be useful,
 
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 
21
 * GNU General Public License for more details.
 
22
 *
 
23
 * ============================================================ */
 
24
 
 
25
#include "searchwidget.moc"
 
26
 
 
27
// Qt includes
 
28
 
 
29
#include <QContextMenuEvent>
 
30
#include <QListView>
 
31
#include <QPainter>
 
32
#include <QPushButton>
 
33
#include <QToolButton>
 
34
#include <QTreeView>
 
35
#include <QVBoxLayout>
 
36
#include <QWidget>
 
37
 
 
38
// KDE includes
 
39
 
 
40
#include <kaction.h>
 
41
#include <kcombobox.h>
 
42
#include <kconfiggroup.h>
 
43
#include <khbox.h>
 
44
#include <klineedit.h>
 
45
#include <klocale.h>
 
46
#include <kmenu.h>
 
47
#include <kmessagebox.h>
 
48
#include <kstandarddirs.h>
 
49
#include <kvbox.h>
 
50
 
 
51
// Libkgeomap includes
 
52
 
 
53
#include <libkgeomap/kgeomap_widget.h>
 
54
 
 
55
// local includes
 
56
 
 
57
#include "searchbackend.h"
 
58
#include "gpssync_common.h"
 
59
#include "gpsbookmarkowner.h"
 
60
#include "gpsundocommand.h"
 
61
#include "kipiimagemodel.h"
 
62
 
 
63
#ifdef GPSSYNC_MODELTEST
 
64
#include <modeltest.h>
 
65
#endif /* GPSSYNC_MODELTEST */
 
66
 
 
67
namespace KIPIGPSSyncPlugin
 
68
{
 
69
 
 
70
static int QItemSelectionModel_selectedRowsCount(const QItemSelectionModel* const selectionModel)
 
71
{
 
72
    if (!selectionModel->hasSelection())
 
73
    {
 
74
        return 0;
 
75
    }
 
76
 
 
77
    return selectionModel->selectedRows().count();
 
78
}
 
79
 
 
80
class SearchWidgetPrivate
 
81
{
 
82
public:
 
83
    SearchWidgetPrivate()
 
84
    {
 
85
        // TODO: initialize in initializer list
 
86
        searchInProgress = false;
 
87
        actionToggleAllResultsVisibilityIconUnchecked=KIcon(SmallIcon("layer-visible-off"));
 
88
        actionToggleAllResultsVisibilityIconChecked=KIcon(SmallIcon("layer-visible-on"));
 
89
    }
 
90
 
 
91
    // Map
 
92
    KGeoMap::KGeoMapWidget                          *mapWidget;
 
93
    GPSBookmarkOwner                          *gpsBookmarkOwner;
 
94
    KipiImageModel                            *kipiImageModel;
 
95
    QItemSelectionModel                       *kipiImageSelectionModel;
 
96
    KLineEdit                                 *searchTermLineEdit;
 
97
    QPushButton                               *searchButton;
 
98
 
 
99
    // Search: backend
 
100
    SearchBackend                             *searchBackend;
 
101
    SearchResultModel                         *searchResultsModel;
 
102
    QItemSelectionModel                       *searchResultsSelectionModel;
 
103
    SearchResultModelHelper                   *searchResultModelHelper;
 
104
 
 
105
    // Search: UI
 
106
    QTreeView                                 *treeView;
 
107
    QVBoxLayout                               *mainVBox;
 
108
    KComboBox                                 *backendSelectionBox;
 
109
    KAction                                   *actionClearResultsList;
 
110
    KAction                                   *actionKeepOldResults;
 
111
    KAction                                   *actionToggleAllResultsVisibility;
 
112
    bool                                       searchInProgress;
 
113
    KIcon                                      actionToggleAllResultsVisibilityIconUnchecked;
 
114
    KIcon                                      actionToggleAllResultsVisibilityIconChecked;
 
115
    KAction                                   *actionCopyCoordinates;
 
116
    KAction                                   *actionBookmark;
 
117
    KAction                                   *actionMoveImagesToThisResult;
 
118
    KAction                                   *actionRemovedSelectedSearchResultsFromList;
 
119
};
 
120
 
 
121
SearchWidget::SearchWidget(GPSBookmarkOwner* const gpsBookmarkOwner,
 
122
                           KipiImageModel* const kipiImageModel,
 
123
                           QItemSelectionModel* const kipiImageSelectionModel,
 
124
                           QWidget* const parent)
 
125
            : QWidget(parent), d(new SearchWidgetPrivate())
 
126
{
 
127
    d->gpsBookmarkOwner = gpsBookmarkOwner;
 
128
    d->kipiImageModel = kipiImageModel;
 
129
    d->kipiImageSelectionModel = kipiImageSelectionModel;
 
130
    d->searchBackend = new SearchBackend(this);
 
131
    d->searchResultsModel = new SearchResultModel(this);
 
132
#ifdef GPSSYNC_MODELTEST
 
133
    new ModelTest(d->searchResultsModel, this);
 
134
#endif /* GPSSYNC_MODELTEST */
 
135
    d->searchResultsSelectionModel = new QItemSelectionModel(d->searchResultsModel);
 
136
    d->searchResultsModel->setSelectionModel(d->searchResultsSelectionModel);
 
137
    d->searchResultModelHelper = new SearchResultModelHelper(d->searchResultsModel, d->searchResultsSelectionModel, d->kipiImageModel, this);
 
138
 
 
139
    d->mainVBox = new QVBoxLayout(this);
 
140
    setLayout(d->mainVBox);
 
141
 
 
142
    KHBox* const topHBox = new KHBox(this);
 
143
    d->mainVBox->addWidget(topHBox);
 
144
    d->searchTermLineEdit = new KLineEdit(topHBox);
 
145
    d->searchTermLineEdit->setClearButtonShown(true);
 
146
    d->searchButton = new QPushButton(i18nc("Start the search", "Search"), topHBox);
 
147
 
 
148
    KHBox* const actionHBox = new KHBox(this);
 
149
    d->mainVBox->addWidget(actionHBox);
 
150
 
 
151
    d->actionClearResultsList = new KAction(this);
 
152
    d->actionClearResultsList->setIcon(SmallIcon("edit-clear-list"));
 
153
    d->actionClearResultsList->setToolTip(i18n("Clear the search results."));
 
154
    QToolButton* const tbClearResultsList = new QToolButton(actionHBox);
 
155
    tbClearResultsList->setDefaultAction(d->actionClearResultsList);
 
156
 
 
157
    d->actionKeepOldResults = new KAction(this);
 
158
    d->actionKeepOldResults->setIcon(SmallIcon("flag"));
 
159
    d->actionKeepOldResults->setCheckable(true);
 
160
    d->actionKeepOldResults->setChecked(false);
 
161
    d->actionKeepOldResults->setToolTip(i18n("Keep the results of old searches when doing a new search."));
 
162
    QToolButton* const tbKeepOldResults = new QToolButton(actionHBox);
 
163
    tbKeepOldResults->setDefaultAction(d->actionKeepOldResults);
 
164
 
 
165
    d->actionToggleAllResultsVisibility = new KAction(this);
 
166
    d->actionToggleAllResultsVisibility->setCheckable(true);
 
167
    d->actionToggleAllResultsVisibility->setChecked(true);
 
168
    d->actionToggleAllResultsVisibility->setToolTip(i18n("Toggle the visibility of the search results on the map."));
 
169
    QToolButton* const tbToggleAllVisibility = new QToolButton(actionHBox);
 
170
    tbToggleAllVisibility->setDefaultAction(d->actionToggleAllResultsVisibility);
 
171
 
 
172
    d->actionCopyCoordinates = new KAction(i18n("Copy coordinates"), this);
 
173
    d->actionCopyCoordinates->setIcon(SmallIcon("edit-copy"));
 
174
 
 
175
    d->actionMoveImagesToThisResult = new KAction(i18n("Move selected images to this position"), this);
 
176
 
 
177
    d->actionRemovedSelectedSearchResultsFromList = new KAction(i18n("Remove from results list"), this);
 
178
    d->actionRemovedSelectedSearchResultsFromList->setIcon(SmallIcon("list-remove"));
 
179
 
 
180
    d->backendSelectionBox = new KComboBox(actionHBox);
 
181
    d->backendSelectionBox->setToolTip(i18n("Select which service you would like to use."));
 
182
    const QList<QPair<QString, QString> > backendList = d->searchBackend->getBackends();
 
183
    for (int i=0; i<backendList.count(); ++i)
 
184
    {
 
185
        d->backendSelectionBox->addItem(backendList.at(i).first, backendList.at(i).second);
 
186
    }
 
187
    
 
188
    // add stretch after the controls:
 
189
    QHBoxLayout* const hBoxLayout = reinterpret_cast<QHBoxLayout*>(actionHBox->layout());
 
190
    if (hBoxLayout)
 
191
    {
 
192
        hBoxLayout->addStretch();
 
193
    }
 
194
 
 
195
    d->treeView = new QTreeView(this);
 
196
    d->mainVBox->addWidget(d->treeView);
 
197
    d->treeView->setRootIsDecorated(false);
 
198
    d->treeView->setModel(d->searchResultsModel);
 
199
    d->treeView->setSelectionModel(d->searchResultsSelectionModel);
 
200
    d->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
 
201
 
 
202
    d->actionBookmark = new KAction(i18n("Bookmarks"), this);
 
203
    d->actionBookmark->setMenu(d->gpsBookmarkOwner->getMenu());
 
204
 
 
205
    connect(d->actionMoveImagesToThisResult, SIGNAL(triggered(bool)),
 
206
            this, SLOT(slotMoveSelectedImagesToThisResult()));
 
207
 
 
208
    connect(d->searchButton, SIGNAL(clicked()),
 
209
            this, SLOT(slotTriggerSearch()));
 
210
 
 
211
    connect(d->searchBackend, SIGNAL(signalSearchCompleted()),
 
212
            this, SLOT(slotSearchCompleted()));
 
213
 
 
214
    connect(d->searchTermLineEdit, SIGNAL(returnPressed()),
 
215
            this, SLOT(slotTriggerSearch()));
 
216
 
 
217
    connect(d->searchTermLineEdit, SIGNAL(textChanged(QString)),
 
218
            this, SLOT(slotUpdateActionAvailability()));
 
219
 
 
220
    connect(d->searchResultsSelectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)),
 
221
            this, SLOT(slotCurrentlySelectedResultChanged(QModelIndex,QModelIndex)));
 
222
 
 
223
    connect(d->actionClearResultsList, SIGNAL(triggered(bool)),
 
224
            this, SLOT(slotClearSearchResults()));
 
225
 
 
226
    connect(d->actionToggleAllResultsVisibility, SIGNAL(triggered(bool)),
 
227
            this, SLOT(slotVisibilityChanged(bool)));
 
228
 
 
229
    connect(d->actionCopyCoordinates, SIGNAL(triggered(bool)),
 
230
            this, SLOT(slotCopyCoordinates()));
 
231
 
 
232
    connect(d->searchResultModelHelper, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
 
233
            this, SIGNAL(signalUndoCommand(GPSUndoCommand*)));
 
234
 
 
235
    connect(d->actionRemovedSelectedSearchResultsFromList, SIGNAL(triggered(bool)),
 
236
            this, SLOT(slotRemoveSelectedFromResultsList()));
 
237
 
 
238
    d->treeView->installEventFilter(this);
 
239
 
 
240
    slotUpdateActionAvailability();
 
241
}
 
242
 
 
243
SearchWidget::~SearchWidget()
 
244
{
 
245
    delete d;
 
246
}
 
247
 
 
248
void SearchWidget::slotSearchCompleted()
 
249
{
 
250
    d->searchInProgress = false;
 
251
 
 
252
    const QString errorString = d->searchBackend->getErrorMessage();
 
253
    if (!errorString.isEmpty())
 
254
    {
 
255
        KMessageBox::error(this, i18n("Your search failed:\n%1", errorString), i18n("Search failed"));
 
256
        slotUpdateActionAvailability();
 
257
        return;
 
258
    }
 
259
 
 
260
    const SearchBackend::SearchResult::List searchResults = d->searchBackend->getResults();
 
261
    d->searchResultsModel->addResults(searchResults);
 
262
 
 
263
    slotUpdateActionAvailability();
 
264
}
 
265
 
 
266
void SearchWidget::slotTriggerSearch()
 
267
{
 
268
    // this is necessary since this slot is also connected to QLineEdit::returnPressed
 
269
    if (d->searchTermLineEdit->text().isEmpty() || d->searchInProgress)
 
270
    {
 
271
        return;
 
272
    }
 
273
 
 
274
    if (!d->actionKeepOldResults->isChecked())
 
275
    {
 
276
        slotClearSearchResults();
 
277
    }
 
278
 
 
279
    d->searchInProgress = true;
 
280
 
 
281
    const QString searchBackendName = d->backendSelectionBox->itemData(d->backendSelectionBox->currentIndex()).toString();
 
282
    d->searchBackend->search(searchBackendName, d->searchTermLineEdit->text());
 
283
 
 
284
    slotUpdateActionAvailability();
 
285
}
 
286
 
 
287
class SearchResultModelPrivate
 
288
{
 
289
public:
 
290
    SearchResultModelPrivate()
 
291
    {
 
292
        markerNormalUrl = KStandardDirs::locate("data", "gpssync/searchmarker-normal.png");
 
293
        markerNormal = QPixmap(markerNormalUrl.toLocalFile());
 
294
 
 
295
        markerSelectedUrl = KStandardDirs::locate("data", "gpssync/searchmarker-selected.png");
 
296
        markerSelected = QPixmap(markerSelectedUrl.toLocalFile());
 
297
    }
 
298
 
 
299
    QList<SearchResultModel::SearchResultItem> searchResults;
 
300
    KUrl markerNormalUrl;
 
301
    KUrl markerSelectedUrl;
 
302
    QPixmap markerNormal;
 
303
    QPixmap markerSelected;
 
304
    QItemSelectionModel* selectionModel;
 
305
};
 
306
 
 
307
SearchResultModel::SearchResultModel(QObject* const parent)
 
308
: QAbstractItemModel(parent), d(new SearchResultModelPrivate())
 
309
{
 
310
}
 
311
 
 
312
SearchResultModel::~SearchResultModel()
 
313
{
 
314
    delete d;
 
315
}
 
316
 
 
317
int SearchResultModel::columnCount(const QModelIndex& parent) const
 
318
{
 
319
    Q_UNUSED(parent)
 
320
 
 
321
    return 1;
 
322
}
 
323
 
 
324
bool SearchResultModel::setData(const QModelIndex& index, const QVariant& value, int role)
 
325
{
 
326
    Q_UNUSED(index)
 
327
    Q_UNUSED(value)
 
328
    Q_UNUSED(role)
 
329
 
 
330
    return false;
 
331
}
 
332
 
 
333
QVariant SearchResultModel::data(const QModelIndex& index, int role) const
 
334
{
 
335
    const int rowNumber = index.row();
 
336
    if ((rowNumber<0)||(rowNumber>=d->searchResults.count()))
 
337
    {
 
338
        return QVariant();
 
339
    }
 
340
 
 
341
    const int columnNumber = index.column();
 
342
 
 
343
    if (columnNumber==0)
 
344
    {
 
345
        switch (role)
 
346
        {
 
347
        case Qt::DisplayRole:
 
348
            return d->searchResults.at(rowNumber).result.name;
 
349
 
 
350
        case Qt::DecorationRole:
 
351
        {
 
352
            QPixmap markerIcon;
 
353
            getMarkerIcon(index, 0, 0, &markerIcon, 0);
 
354
            return markerIcon;
 
355
        }
 
356
 
 
357
        default:
 
358
            return QVariant();
 
359
        }
 
360
    }
 
361
 
 
362
    return QVariant();
 
363
}
 
364
 
 
365
QModelIndex SearchResultModel::index(int row, int column, const QModelIndex& parent) const
 
366
{
 
367
    if (parent.isValid())
 
368
    {
 
369
        // there are no child items, only top level items
 
370
        return QModelIndex();
 
371
    }
 
372
 
 
373
    if ( (column<0) || (column>=1)
 
374
         || (row<0) || (row>=d->searchResults.count()) )
 
375
    {
 
376
        return QModelIndex();
 
377
    }
 
378
 
 
379
    return createIndex(row, column, 0);
 
380
}
 
381
 
 
382
QModelIndex SearchResultModel::parent(const QModelIndex& index) const
 
383
{
 
384
    Q_UNUSED(index)
 
385
 
 
386
    // we have only top level items
 
387
    return QModelIndex();
 
388
}
 
389
 
 
390
int SearchResultModel::rowCount(const QModelIndex& parent) const
 
391
{
 
392
    if (parent.isValid())
 
393
    {
 
394
        return 0;
 
395
    }
 
396
 
 
397
    return d->searchResults.count();
 
398
}
 
399
 
 
400
bool SearchResultModel::setHeaderData(int section, Qt::Orientation orientation,
 
401
                                      const QVariant& value, int role)
 
402
{
 
403
    Q_UNUSED(section)
 
404
    Q_UNUSED(orientation)
 
405
    Q_UNUSED(value)
 
406
    Q_UNUSED(role)
 
407
 
 
408
    return false;
 
409
}
 
410
 
 
411
QVariant SearchResultModel::headerData(int section, Qt::Orientation orientation, int role) const
 
412
{
 
413
    Q_UNUSED(role)
 
414
 
 
415
    if ((section >= 1) || (orientation != Qt::Horizontal))
 
416
    {
 
417
        return false;
 
418
    }
 
419
 
 
420
    return QVariant("Name");
 
421
}
 
422
 
 
423
Qt::ItemFlags SearchResultModel::flags(const QModelIndex& index) const
 
424
{
 
425
    return QAbstractItemModel::flags(index);
 
426
}
 
427
 
 
428
void SearchResultModel::addResults(const SearchBackend::SearchResult::List& results)
 
429
{
 
430
    // first check which items are not duplicates
 
431
    QList<int> nonDuplicates;
 
432
    for (int i=0; i<results.count(); ++i)
 
433
    {
 
434
        const SearchBackend::SearchResult& currentResult = results.at(i);
 
435
        bool isDuplicate = false;
 
436
        for (int j=0; j<d->searchResults.count(); ++j)
 
437
        {
 
438
            if (currentResult.internalId==d->searchResults.at(j).result.internalId)
 
439
            {
 
440
                isDuplicate = true;
 
441
                break;
 
442
            }
 
443
        }
 
444
        if (!isDuplicate)
 
445
        {
 
446
            nonDuplicates << i;
 
447
        }
 
448
    }
 
449
 
 
450
    if (nonDuplicates.isEmpty())
 
451
    {
 
452
        return;
 
453
    }
 
454
 
 
455
    beginInsertRows(QModelIndex(), d->searchResults.count(), d->searchResults.count()+nonDuplicates.count()-1);
 
456
    for (int i=0; i<nonDuplicates.count(); ++i)
 
457
    {
 
458
        SearchResultItem item;
 
459
        item.result = results.at(nonDuplicates.at(i));
 
460
        d->searchResults << item;
 
461
    }
 
462
    endInsertRows();
 
463
}
 
464
 
 
465
class SearchResultModelHelperPrivate
 
466
{
 
467
public:
 
468
    SearchResultModelHelperPrivate()
 
469
    : model(0),
 
470
      selectionModel(0),
 
471
      imageModel(0),
 
472
      visible(true)
 
473
    {
 
474
    }
 
475
 
 
476
    SearchResultModel* model;
 
477
    QItemSelectionModel* selectionModel;
 
478
    KipiImageModel* imageModel;
 
479
    bool visible;
 
480
};
 
481
 
 
482
SearchResultModelHelper::SearchResultModelHelper(SearchResultModel* const resultModel,
 
483
                                                 QItemSelectionModel* const selectionModel,
 
484
                                                 KipiImageModel* const imageModel,
 
485
                                                 QObject* const parent)
 
486
: KGeoMap::ModelHelper(parent), d(new SearchResultModelHelperPrivate())
 
487
{
 
488
    d->model = resultModel;
 
489
    d->selectionModel = selectionModel;
 
490
    d->imageModel = imageModel;
 
491
}
 
492
 
 
493
SearchResultModelHelper::~SearchResultModelHelper()
 
494
{
 
495
    delete d;
 
496
}
 
497
 
 
498
QAbstractItemModel* SearchResultModelHelper::model() const
 
499
{
 
500
    return d->model;
 
501
}
 
502
 
 
503
QItemSelectionModel* SearchResultModelHelper::selectionModel() const
 
504
{
 
505
    return d->selectionModel;
 
506
}
 
507
 
 
508
bool SearchResultModelHelper::itemCoordinates(const QModelIndex& index, KGeoMap::GeoCoordinates* const coordinates) const
 
509
{
 
510
    const SearchResultModel::SearchResultItem item = d->model->resultItem(index);
 
511
 
 
512
    *coordinates = item.result.coordinates;
 
513
 
 
514
    return true;
 
515
}
 
516
 
 
517
bool SearchResultModelHelper::itemIcon(const QModelIndex& index, QPoint* const offset, QSize* const size, QPixmap* const pixmap, KUrl* const url) const
 
518
{
 
519
    return d->model->getMarkerIcon(index, offset, size, pixmap, url);
 
520
}
 
521
 
 
522
SearchResultModel::SearchResultItem SearchResultModel::resultItem(const QModelIndex& index) const
 
523
{
 
524
    if (!index.isValid())
 
525
    {
 
526
        return SearchResultItem();
 
527
    }
 
528
 
 
529
    return d->searchResults.at(index.row());
 
530
}
 
531
 
 
532
KGeoMap::ModelHelper* SearchWidget::getModelHelper()
 
533
{
 
534
    return d->searchResultModelHelper;
 
535
}
 
536
 
 
537
bool SearchResultModel::getMarkerIcon(const QModelIndex& index, QPoint* const offset, QSize* const size, QPixmap* const pixmap, KUrl* const url) const
 
538
{
 
539
    // determine the id of the marker
 
540
    const int markerNumber = index.row();
 
541
 
 
542
    const bool itemIsSelected = d->selectionModel ? d->selectionModel->isSelected(index) : false;
 
543
    QPixmap markerPixmap = itemIsSelected ? d->markerSelected : d->markerNormal;
 
544
 
 
545
    // if the caller requests a URL and the marker will not get
 
546
    // a special label, return a URL. Otherwise, return a pixmap.
 
547
    const bool returnViaUrl = url && markerNumber>26;
 
548
    if (returnViaUrl)
 
549
    {
 
550
        *url = itemIsSelected ? d->markerSelectedUrl : d->markerNormalUrl;
 
551
 
 
552
        if (size)
 
553
        {
 
554
            *size = markerPixmap.size();
 
555
        }
 
556
    }
 
557
    else
 
558
    {
 
559
        if (markerNumber<=26)
 
560
        {
 
561
            const QString markerId = QChar('A'+markerNumber);
 
562
 
 
563
            QPainter painter(&markerPixmap);
 
564
            painter.setRenderHint(QPainter::Antialiasing);
 
565
            painter.setPen(Qt::black);
 
566
            QRect textRect(0,2,markerPixmap.width(),markerPixmap.height());
 
567
            painter.drawText(textRect, Qt::AlignHCenter, markerId);
 
568
        }
 
569
 
 
570
        *pixmap = markerPixmap;
 
571
    }
 
572
 
 
573
    if (offset)
 
574
    {
 
575
        *offset = QPoint(markerPixmap.width()/2, markerPixmap.height()-1);
 
576
    }
 
577
 
 
578
    return true;
 
579
}
 
580
 
 
581
void SearchResultModel::setSelectionModel(QItemSelectionModel* const selectionModel)
 
582
{
 
583
    d->selectionModel = selectionModel;
 
584
}
 
585
 
 
586
void SearchWidget::slotCurrentlySelectedResultChanged(const QModelIndex& current, const QModelIndex& previous)
 
587
{
 
588
    Q_UNUSED(previous)
 
589
 
 
590
    if (!current.isValid())
 
591
    {
 
592
        return;
 
593
    }
 
594
 
 
595
    const SearchResultModel::SearchResultItem currentItem = d->searchResultsModel->resultItem(current);
 
596
 
 
597
    if (d->mapWidget)
 
598
    {
 
599
        d->mapWidget->setCenter(currentItem.result.coordinates);
 
600
    }
 
601
}
 
602
 
 
603
void SearchWidget::slotClearSearchResults()
 
604
{
 
605
    d->searchResultsModel->clearResults();
 
606
 
 
607
    slotUpdateActionAvailability();
 
608
}
 
609
 
 
610
void SearchResultModel::clearResults()
 
611
{
 
612
    beginResetModel();
 
613
    d->searchResults.clear();
 
614
    endResetModel();
 
615
}
 
616
 
 
617
void SearchWidget::slotVisibilityChanged(bool state)
 
618
{
 
619
    d->searchResultModelHelper->setVisibility(state);
 
620
    slotUpdateActionAvailability();
 
621
}
 
622
 
 
623
void SearchResultModelHelper::setVisibility(const bool state)
 
624
{
 
625
    d->visible = state;
 
626
    emit(signalVisibilityChanged());
 
627
}
 
628
 
 
629
void SearchWidget::slotUpdateActionAvailability()
 
630
{
 
631
    const int nSelectedResults = QItemSelectionModel_selectedRowsCount(d->searchResultsSelectionModel);
 
632
    const bool haveOneSelectedResult = nSelectedResults == 1;
 
633
    const bool haveSelectedImages = !d->kipiImageSelectionModel->selectedRows().isEmpty();
 
634
 
 
635
    d->actionCopyCoordinates->setEnabled(haveOneSelectedResult);
 
636
    d->actionMoveImagesToThisResult->setEnabled(haveOneSelectedResult && haveSelectedImages);
 
637
    d->actionRemovedSelectedSearchResultsFromList->setEnabled(nSelectedResults>=1);
 
638
 
 
639
    const bool haveSearchText = !d->searchTermLineEdit->text().isEmpty();
 
640
 
 
641
    d->searchButton->setEnabled(haveSearchText&&!d->searchInProgress);
 
642
    d->actionClearResultsList->setEnabled(d->searchResultsModel->rowCount()>0);
 
643
    d->actionToggleAllResultsVisibility->setIcon(
 
644
            d->actionToggleAllResultsVisibility->isChecked() ?
 
645
            d->actionToggleAllResultsVisibilityIconChecked :
 
646
            d->actionToggleAllResultsVisibilityIconUnchecked
 
647
        );
 
648
}
 
649
 
 
650
bool SearchWidget::eventFilter(QObject *watched, QEvent *event)
 
651
{
 
652
    if (watched==d->treeView)
 
653
    {
 
654
        // we are only interested in context-menu events:
 
655
        if (event->type()==QEvent::ContextMenu)
 
656
        {
 
657
            if (d->searchResultsSelectionModel->hasSelection())
 
658
            {
 
659
                const QModelIndex currentIndex = d->searchResultsSelectionModel->currentIndex();
 
660
                const SearchResultModel::SearchResultItem searchResult = d->searchResultsModel->resultItem(currentIndex);
 
661
                d->gpsBookmarkOwner->setPositionAndTitle(searchResult.result.coordinates, searchResult.result.name);
 
662
            }
 
663
 
 
664
            slotUpdateActionAvailability();
 
665
 
 
666
            // construct the context-menu:
 
667
            KMenu* const menu = new KMenu(d->treeView);
 
668
            menu->addAction(d->actionCopyCoordinates);
 
669
            menu->addAction(d->actionMoveImagesToThisResult);
 
670
            menu->addAction(d->actionRemovedSelectedSearchResultsFromList);
 
671
//             menu->addAction(d->actionBookmark);
 
672
            d->gpsBookmarkOwner->changeAddBookmark(true);
 
673
 
 
674
            QContextMenuEvent* const e = static_cast<QContextMenuEvent*>(event);
 
675
            menu->exec(e->globalPos());
 
676
            delete menu;
 
677
        }
 
678
    }
 
679
 
 
680
    return QObject::eventFilter(watched, event);
 
681
}
 
682
 
 
683
void SearchWidget::slotCopyCoordinates()
 
684
{
 
685
    const QModelIndex currentIndex = d->searchResultsSelectionModel->currentIndex();
 
686
    const SearchResultModel::SearchResultItem currentItem = d->searchResultsModel->resultItem(currentIndex);
 
687
 
 
688
    CoordinatesToClipboard(currentItem.result.coordinates, KUrl(), currentItem.result.name);
 
689
}
 
690
 
 
691
void SearchWidget::saveSettingsToGroup(KConfigGroup* const group)
 
692
{
 
693
    group->writeEntry("Keep old results", d->actionKeepOldResults->isChecked());
 
694
    group->writeEntry("Search backend", d->backendSelectionBox->itemData(d->backendSelectionBox->currentIndex()).toString());
 
695
 
 
696
    slotUpdateActionAvailability();
 
697
}
 
698
 
 
699
void SearchWidget::readSettingsFromGroup(const KConfigGroup* const group)
 
700
{
 
701
    d->actionKeepOldResults->setChecked(group->readEntry("Keep old results", false));
 
702
    const QString backendId = group->readEntry("Search backend", "osm");
 
703
    for (int i=0; i<d->backendSelectionBox->count(); ++i)
 
704
    {
 
705
        if (d->backendSelectionBox->itemData(i).toString()==backendId)
 
706
        {
 
707
            d->backendSelectionBox->setCurrentIndex(i);
 
708
            break;
 
709
        }
 
710
    }
 
711
}
 
712
 
 
713
KGeoMap::ModelHelper::Flags SearchResultModelHelper::modelFlags() const
 
714
{
 
715
    return FlagSnaps|(d->visible?FlagVisible:FlagNull);
 
716
}
 
717
 
 
718
KGeoMap::ModelHelper::Flags SearchResultModelHelper::itemFlags(const QModelIndex& /*index*/) const
 
719
{
 
720
    return FlagVisible|FlagSnaps;
 
721
}
 
722
 
 
723
void SearchResultModelHelper::snapItemsTo(const QModelIndex& targetIndex, const QList<QModelIndex>& snappedIndices)
 
724
{
 
725
    GPSUndoCommand* const undoCommand = new GPSUndoCommand();
 
726
 
 
727
    SearchResultModel::SearchResultItem targetItem = d->model->resultItem(targetIndex);
 
728
    const KGeoMap::GeoCoordinates& targetCoordinates = targetItem.result.coordinates;
 
729
    for (int i=0; i<snappedIndices.count(); ++i)
 
730
    {
 
731
        const QPersistentModelIndex itemIndex = snappedIndices.at(i);
 
732
        KipiImageItem* const item = d->imageModel->itemFromIndex(itemIndex);
 
733
 
 
734
        GPSUndoCommand::UndoInfo undoInfo(itemIndex);
 
735
        undoInfo.readOldDataFromItem(item);
 
736
 
 
737
        GPSDataContainer newData;
 
738
        newData.setCoordinates(targetCoordinates);
 
739
        item->setGPSData(newData);
 
740
 
 
741
        undoInfo.readNewDataFromItem(item);
 
742
 
 
743
        undoCommand->addUndoInfo(undoInfo);
 
744
    }
 
745
    undoCommand->setText(i18np("1 image snapped to '%2'",
 
746
                               "%1 images snapped to '%2'", snappedIndices.count(), targetItem.result.name));
 
747
 
 
748
    emit(signalUndoCommand(undoCommand));
 
749
}
 
750
 
 
751
void SearchWidget::slotMoveSelectedImagesToThisResult()
 
752
{
 
753
    const QModelIndex currentIndex = d->searchResultsSelectionModel->currentIndex();
 
754
    const SearchResultModel::SearchResultItem currentItem = d->searchResultsModel->resultItem(currentIndex);
 
755
    const KGeoMap::GeoCoordinates& targetCoordinates = currentItem.result.coordinates;
 
756
 
 
757
    const QModelIndexList selectedImageIndices = d->kipiImageSelectionModel->selectedRows();
 
758
    if (selectedImageIndices.isEmpty())
 
759
    {
 
760
        return;
 
761
    }
 
762
 
 
763
    GPSUndoCommand* const undoCommand = new GPSUndoCommand();
 
764
    for (int i=0; i<selectedImageIndices.count(); ++i)
 
765
    {
 
766
        const QPersistentModelIndex itemIndex = selectedImageIndices.at(i);
 
767
        KipiImageItem* const item = d->kipiImageModel->itemFromIndex(itemIndex);
 
768
 
 
769
        GPSUndoCommand::UndoInfo undoInfo(itemIndex);
 
770
        undoInfo.readOldDataFromItem(item);        
 
771
 
 
772
        GPSDataContainer newData;
 
773
        newData.setCoordinates(targetCoordinates);
 
774
        item->setGPSData(newData);
 
775
 
 
776
        undoInfo.readNewDataFromItem(item);
 
777
 
 
778
        undoCommand->addUndoInfo(undoInfo);
 
779
    }
 
780
    undoCommand->setText(i18np("1 image moved to '%2'",
 
781
                               "%1 images moved to '%2'", selectedImageIndices.count(), currentItem.result.name));
 
782
 
 
783
    emit(signalUndoCommand(undoCommand));
 
784
}
 
785
 
 
786
void SearchWidget::setPrimaryMapWidget(KGeoMap::KGeoMapWidget* const mapWidget)
 
787
{
 
788
    d->mapWidget = mapWidget;
 
789
}
 
790
 
 
791
void SearchResultModel::removeRowsByIndexes(const QModelIndexList& rowsList)
 
792
{
 
793
    // extract the row numbers first:
 
794
    QList<int> rowNumbers;
 
795
    Q_FOREACH(const QModelIndex& index, rowsList)
 
796
    {
 
797
        if (index.isValid())
 
798
        {
 
799
            rowNumbers << index.row();
 
800
        }
 
801
    }
 
802
    if (rowNumbers.isEmpty())
 
803
    {
 
804
        return;
 
805
    }
 
806
 
 
807
    qSort(rowNumbers.begin(), rowNumbers.end());
 
808
 
 
809
    // now delete the rows, starting with the last row:
 
810
    for (int i=rowNumbers.count()-1; i>=0; i--)
 
811
    {
 
812
        const int rowNumber = rowNumbers.at(i);
 
813
 
 
814
        /// @todo This is very slow for several indexes, because the views update after every removal
 
815
        beginRemoveRows(QModelIndex(), rowNumber, rowNumber);
 
816
        d->searchResults.removeAt(rowNumber);
 
817
        endRemoveRows();
 
818
    }
 
819
}
 
820
 
 
821
static bool RowRangeLessThan(const QPair<int, int>& a, const QPair<int, int>& b)
 
822
{
 
823
    return a.first < b.first;
 
824
}
 
825
 
 
826
void SearchResultModel::removeRowsBySelection(const QItemSelection& selectionList)
 
827
{
 
828
    // extract the row numbers first:
 
829
    QList<QPair<int, int> > rowRanges;
 
830
    Q_FOREACH(const QItemSelectionRange& range, selectionList)
 
831
    {
 
832
        rowRanges << QPair<int, int>(range.top(), range.bottom());
 
833
    }
 
834
 
 
835
    // we expect the ranges to be sorted here
 
836
    qSort(rowRanges.begin(), rowRanges.end(), RowRangeLessThan);
 
837
 
 
838
    // now delete the rows, starting with the last row:
 
839
    for (int i=rowRanges.count()-1; i>=0; i--)
 
840
    {
 
841
        const QPair<int, int> currentRange = rowRanges.at(i);
 
842
 
 
843
        /// @todo This is very slow for several indexes, because the views update after every removal
 
844
        beginRemoveRows(QModelIndex(), currentRange.first, currentRange.second);
 
845
        for (int j=currentRange.second; j>=currentRange.first; j--)
 
846
        {
 
847
            d->searchResults.removeAt(j);
 
848
        }
 
849
        endRemoveRows();
 
850
    }
 
851
}
 
852
 
 
853
void SearchWidget::slotRemoveSelectedFromResultsList()
 
854
{
 
855
    const QItemSelection selectedRows = d->searchResultsSelectionModel->selection();
 
856
    if (selectedRows.isEmpty())
 
857
    {
 
858
        return;
 
859
    }
 
860
 
 
861
    d->searchResultsModel->removeRowsBySelection(selectedRows);
 
862
 
 
863
    slotUpdateActionAvailability();
 
864
}
 
865
 
 
866
} /* KIPIGPSSyncPlugin */