玉兔远程控制 0.1.0-bate6
载入中...
搜索中...
未找到
FrmHistory.cpp
1// Author: Kang Lin <kl222@126.com>
2
3#include <QFileDialog>
4#include <QApplication>
5#include <QClipboard>
6#include <QMessageBox>
7#include <QMenu>
8#include <QLoggingCategory>
9#include <QToolBar>
10#include <QLayout>
11#include <QVBoxLayout>
12#include <QComboBox>
13#include <QDateEdit>
14#include <QSpinBox>
15
16#include "FrmHistory.h"
17#include "RabbitCommonDir.h"
18#include "ui_FrmHistory.h"
19
20static Q_LOGGING_CATEGORY(log, "WebBrowser.History")
22 QWidget *parent)
23 : QWidget(parent)
24 , ui(new Ui::CFrmHistory)
25 , m_pModelHistory(nullptr)
26 , m_pPara(pPara)
27 , m_pDateStart(nullptr)
28 , m_pDateEnd(nullptr)
29{
30 bool check = false;
31 ui->setupUi(this);
32 ui->treeView->hide();
33
34 setWindowTitle(tr("History"));
35
36 QToolBar* pToolBar = new QToolBar(this);
37
38 QDate curDate = QDate::currentDate();
39 m_pDateStart = new QDateEdit(curDate.addDays(-7), pToolBar);
40 if(m_pDateStart)
41 m_pDateStart->setToolTip(tr("Start date"));
42 m_pDateEnd = new QDateEdit(curDate, pToolBar);
43 if(m_pDateEnd)
44 m_pDateEnd->setToolTip(tr("End date"));
45 QComboBox* pCB = new QComboBox(pToolBar);
46 pCB->addItem(tr("One day"), 1);
47 pCB->addItem(tr("Two days"), 2);
48 pCB->addItem(tr("One Week"), 7);
49 pCB->addItem(tr("One month"), curDate.daysInMonth());
50 pCB->setCurrentIndex(2);
51 check = connect(pCB, SIGNAL(currentIndexChanged(int)),
52 this, SLOT(slotComboxIndexChanged(int)));
53 Q_ASSERT(check);
54
55 pToolBar->addWidget(pCB);
56 pToolBar->addWidget(m_pDateStart);
57 pToolBar->addWidget(m_pDateEnd);
58
59 QAction* pRefresh = pToolBar->addAction(
60 QIcon::fromTheme("view-refresh"), tr("Refresh"),
61 this, &CFrmHistory::slotRefresh);
62 if(pRefresh) {
63 pRefresh->setToolTip(pRefresh->text());
64 pRefresh->setStatusTip(pRefresh->text());
65 }
66
67 pToolBar->addSeparator();
68 QSpinBox* pSBLimit = new QSpinBox(pToolBar);
69 if(pSBLimit) {
70 pToolBar->addWidget(pSBLimit);
71 pSBLimit->setToolTip(tr("Limit"));
72 int nMax = 1000;
73 if(m_pPara)
74 nMax = qMax(nMax, m_pPara->GetDatabaseViewLimit());
75 pSBLimit->setRange(-1, nMax);
76 if(m_pPara)
77 pSBLimit->setValue(m_pPara->GetDatabaseViewLimit());
78 check = connect(pSBLimit, SIGNAL(valueChanged(int)), this, SLOT(slotLimit(int)));
79 Q_ASSERT(check);
80 }
81
82 pToolBar->addSeparator();
83 pToolBar->addAction(
84 QIcon::fromTheme("import"), tr("Import"), this, SLOT(slotImport()));
85 pToolBar->addAction(
86 QIcon::fromTheme("export"), tr("Export"), this, SLOT(slotExport()));
87 pToolBar->addSeparator();
88 /*
89 QAction* pSettings = pToolBar->addAction(
90 QIcon::fromTheme("system-settings"), tr("Settings"),
91 this, [&]() {
92 //TODO: add settings ui
93 });
94 if(pSettings) {
95 pSettings->setToolTip(pSettings->text());
96 pSettings->setStatusTip(pSettings->text());
97 }//*/
98
99 pToolBar->addAction(QIcon::fromTheme("window-close"), tr("Close"), this, SLOT(close()));
100
101 QLayout* pLayout = nullptr;
102 pLayout = new QVBoxLayout(this);
103 pLayout->addWidget(pToolBar);
104 pLayout->addWidget(ui->splitter);
105 setLayout(pLayout);
106
107 check = connect(ui->tableView, &QTableView::customContextMenuRequested,
108 this, &CFrmHistory::slotCustomContextMenuRequested);
109 //ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
110 ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
111 ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
112
113 m_pModelHistory = new CHistoryModel(m_pPara, this);
114 if(m_pModelHistory) {
115 ui->tableView->setModel(m_pModelHistory);
116 }
117
118 resize(m_pPara->GetWindowSize());
119
120 slotRefresh();
121}
122
123CFrmHistory::~CFrmHistory()
124{
125 m_pPara->SetWindowSize(this->size());
126 delete ui;
127}
128
129void CFrmHistory::slotRefresh()
130{
131 if(m_pModelHistory) {
132 m_pModelHistory->refresh(m_pDateStart->date(), m_pDateEnd->date());
133 ui->tableView->resizeColumnsToContents();
134 }
135}
136
137void CFrmHistory::on_tableView_doubleClicked(const QModelIndex &index)
138{
139 if(!index.isValid() || !m_pModelHistory) return;
140 auto item = m_pModelHistory->getItem(index);
141 emit sigOpenUrl(item.url);
142}
143
144void CFrmHistory::slotCustomContextMenuRequested(const QPoint &pos)
145{
146 auto index = ui->tableView->indexAt(pos);
147 QItemSelectionModel *selectionModel = ui->tableView->selectionModel();
148 QModelIndexList selectedIndexes;
149 if(selectionModel)
150 selectedIndexes = selectionModel->selectedRows();
151
152 QMenu menu(this);
153
154 if (selectedIndexes.count() > 1) {
155 // 有选中行的情况
156 int selectedCount = selectedIndexes.size();
157
158 // 批量打开
159 QAction *openSelectedAction = menu.addAction(
160 QIcon::fromTheme("document-open"), tr("Open the selected %1 urls").arg(selectedCount));
161 if(openSelectedAction) {
162 connect(openSelectedAction, &QAction::triggered, this, [this, selectedIndexes]() {
163 onOpenSelectedUrls(selectedIndexes);
164 });
165 }
166
167 // 批量删除
168 QAction *deleteSelectedAction = menu.addAction(
169 QIcon::fromTheme("edit-delete"), tr("Delete the selected %1 urls").arg(selectedCount));
170 if(deleteSelectedAction) {
171 connect(deleteSelectedAction, &QAction::triggered, this, [this, selectedIndexes]() {
172 onDeleteSelectedItems(selectedIndexes);
173 });
174 }
175
176 menu.addSeparator();
177
178 // 全选
179 QAction *selectAllAction = menu.addAction(
180 QIcon(":/icons/select_all.png"), tr("All selected"));
181 if(selectAllAction)
182 connect(selectAllAction, &QAction::triggered, ui->tableView, &QTableView::selectAll);
183
184 // 取消选择
185 QAction *deselectAction = menu.addAction(
186 QIcon(":/icons/deselect.png"), tr("Cancel selected"));
187 if(deselectAction)
188 connect(deselectAction, &QAction::triggered,
189 selectionModel, &QItemSelectionModel::clearSelection);
190 } else if (index.isValid()) {
191 // 获取当前行的数据
192 QString url = ui->tableView->model()->data(
193 index.siblingAtColumn(CHistoryModel::ColumnUrl)).toString();
194 QString title = ui->tableView->model()->data(
195 index.siblingAtColumn(CHistoryModel::ColumnTitle)).toString();
196
197 // 打开操作
198 QAction *openAction = menu.addAction(QIcon::fromTheme("document-open"),
199 tr("Open"));
200 if(openAction)
201 connect(openAction, &QAction::triggered, this, [this, url]() {
202 emit sigOpenUrl(url);
203 });
204
205 // 在新标签页中打开
206 QAction *openNewTabAction = menu.addAction(
207 QIcon::fromTheme("document-open"),
208 tr("Open in new tab"));
209 if(openNewTabAction)
210 connect(openNewTabAction, &QAction::triggered, this, [this, url]() {
211 emit sigOpenUrlInNewTab(url);
212 });
213
214 // 复制URL
215 QAction *copyUrlAction = menu.addAction(QIcon::fromTheme("edit-copy"),
216 tr("Copy url"));
217 if(copyUrlAction)
218 connect(copyUrlAction, &QAction::triggered, this, [url]() {
219 QApplication::clipboard()->setText(url);
220 });
221
222 // 复制标题
223 QAction *copyTitleAction = menu.addAction(
224 QIcon::fromTheme("edit-copy"),
225 tr("Copy title"));
226 if(copyTitleAction)
227 connect(copyTitleAction, &QAction::triggered, this, [title]() {
228 QApplication::clipboard()->setText(title);
229 });
230
231 menu.addSeparator();
232
233 // 删除操作
234 QAction *deleteAction = menu.addAction(
235 QIcon::fromTheme("edit-delete"), tr("Delete"));
236 if(deleteAction)
237 connect(deleteAction, &QAction::triggered, this, [this, index]() {
238 onDeleteHistoryItem(index);
239 });
240
241 QAction *deleteAllAction = menu.addAction(
242 QIcon::fromTheme("edit-delete"),
243 tr("Delete all urls %1").arg(title.left(30)));
244 if(deleteAllAction)
245 connect(deleteAllAction, &QAction::triggered, this, [this, url]() {
246 m_pModelHistory->removeItems(url);
247 });
248
249 // 删除所有类似网站
250 QString domain = extractDomain(url);
251 if (!domain.isEmpty()) {
252 QAction *deleteDomainAction = menu.addAction(
253 QIcon::fromTheme("edit-delete"),
254 tr("Delete all urls from %1").arg(domain));
255 if(deleteDomainAction)
256 connect(deleteDomainAction, &QAction::triggered, this, [this, domain]() {
257 onDeleteHistoryByDomain(domain);
258 });
259 }
260
261 menu.addSeparator();
262
263 // 属性/详细信息
264 QAction *propertiesAction = menu.addAction(
265 QIcon::fromTheme("document-properties"), tr("Properties"));
266 if(propertiesAction)
267 connect(propertiesAction, &QAction::triggered, this, [this, index]() {
268 onShowHistoryProperties(index);
269 });
270 }
271
272 menu.addSeparator();
273
274 // 总是显示的菜单项
275 QAction *refreshAction = menu.addAction(
276 QIcon::fromTheme("view-refresh"), tr("Refresh"));
277 if(refreshAction)
278 connect(refreshAction, &QAction::triggered, this, [&]() {
279 if(m_pModelHistory)
280 m_pModelHistory->refresh();
281 });
282
283 QAction *clearAllAction = menu.addAction(
284 QIcon::fromTheme("edit-clear"), tr("Clear all urls"));
285 if(clearAllAction)
286 connect(clearAllAction, &QAction::triggered, this, [&]() {
287 if(m_pModelHistory->removeRows(0, m_pModelHistory->rowCount()));
288 });
289
290 // 设置菜单
291 // QAction *settingsAction = menu.addAction(
292 // QIcon::fromTheme("system-settings"), tr("Settings"));
293 // connect(settingsAction, &QAction::triggered, this, &CFrmHistory::showHistorySettings);
294
295 // 4. 显示菜单
296 menu.exec(ui->tableView->viewport()->mapToGlobal(pos));
297}
298
299QString CFrmHistory::extractDomain(const QString &url)
300{
301 QUrl qurl(url);
302 if (qurl.isValid()) {
303 QString host = qurl.host();
304 // 移除 www. 前缀
305 if (host.startsWith("www.")) {
306 host = host.mid(4);
307 }
308 return host;
309 }
310 return QString();
311}
312
313void CFrmHistory::onDeleteHistoryItem(const QModelIndex &index)
314{
315 if (!index.isValid() || !m_pModelHistory) {
316 return;
317 }
318
319 QString title = ui->tableView->model()->data(
320 index.siblingAtColumn(CHistoryModel::ColumnTitle)).toString();
321 if(title.isEmpty()) {
322 title = ui->tableView->model()->data(
323 index.siblingAtColumn(CHistoryModel::ColumnUrl)).toString();
324 }
325 // 确认对话框
326 QMessageBox::StandardButton reply = QMessageBox::question(
327 this,
328 tr("Delete the url"),
329 tr("Are you sure you want to delete the url \"%1\"?").arg(title),
330 QMessageBox::Yes | QMessageBox::No,
331 QMessageBox::No
332 );
333
334 if (reply == QMessageBox::Yes) {
335 // 从模型中删除
336 if (m_pModelHistory->removeRow(index.row())) {
337 qDebug(log) << "History item deleted";
338 }
339 }
340}
341
342void CFrmHistory::onDeleteHistoryByDomain(const QString &domain)
343{
344 if (domain.isEmpty() || !m_pModelHistory) {
345 return;
346 }
347
348 // 确认对话框
349 QMessageBox::StandardButton reply = QMessageBox::question(
350 this,
351 tr("Delete the url"),
352 tr("Are you sure you want to delete all url from \"%1\"?").arg(domain),
353 QMessageBox::Yes | QMessageBox::No,
354 QMessageBox::No
355 );
356
357 if (reply == QMessageBox::Yes) {
358 m_pModelHistory->removeDomainItems(domain);
359 }
360}
361
362void CFrmHistory::onShowHistoryProperties(const QModelIndex &index)
363{
364 if (!index.isValid() || !m_pModelHistory) {
365 return;
366 }
367
368 // 获取详细数据
369 auto item = m_pModelHistory->getItem(index);
370 // 创建详细信息对话框
371 QString details = QString(
372 "<h3>%1</h3>"
373 "<table border='0' cellspacing='5'>"
374 "<tr><td><b>%2</b></td><td>%3</td></tr>"
375 "<tr><td><b>%4</b></td><td>%5</td></tr>"
376 "</table>")
377 .arg(item.title.toHtmlEscaped())
378 .arg(tr("Url:"))
379 .arg(item.url.toHtmlEscaped())
380 .arg(tr("Visit Time:"))
381 .arg(item.visitTime.toString(QLocale::system().dateFormat()))
382 ;
383
384 QMessageBox::information(this, tr("Properties"), details);
385}
386
387void CFrmHistory::onOpenSelectedUrls(const QModelIndexList &indexes)
388{
389 if (indexes.isEmpty()) return;
390 for (const QModelIndex &index : indexes) {
391 QString url = ui->tableView->model()->data(
392 index.siblingAtColumn(CHistoryModel::ColumnUrl)).toString();
393 if (!url.isEmpty()) {
394 emit sigOpenUrlInNewTab(url);
395 }
396 }
397}
398
399void CFrmHistory::onDeleteSelectedItems(const QModelIndexList &indexes)
400{
401 if (indexes.isEmpty() || !m_pModelHistory) return;
402
403 QMessageBox::StandardButton reply = QMessageBox::question(
404 this,
405 tr("Delete the urls"),
406 tr("Are you sure you want to delete the selected %1 urls?").arg(indexes.size()),
407 QMessageBox::Yes | QMessageBox::No,
408 QMessageBox::No
409 );
410
411 if (reply == QMessageBox::Yes) {
412 // 按行号降序排序,这样删除时索引不会乱
413 QList<int> rows;
414 for (const QModelIndex &index : indexes) {
415 rows.append(index.row());
416 }
417
418 // 降序排序
419 std::sort(rows.begin(), rows.end(), std::greater<int>());
420
421 // 批量删除
422 for (int row : rows) {
423 m_pModelHistory->removeRow(row);
424 }
425
426 qDebug(log) << "Deleted" << indexes.size() << "history items";
427 }
428}
429
430void CFrmHistory::slotImport()
431{
432 QString filename = QFileDialog::getOpenFileName(
433 this, tr("Import histories"),
434 RabbitCommon::CDir::Instance()->GetDirUserDocument(),
435 tr("JSON (*.json);; CSV file (*.csv);; All files (*.*)"));
436
437 if (!filename.isEmpty()) {
438 QFileInfo fi(filename);
439 if(0 == fi.suffix().compare("json", Qt::CaseInsensitive)) {
440 if (m_pModelHistory->importFromJson(filename)) {
441 slotRefresh();
442 QMessageBox::information(this, tr("Success"), tr("Histories import from json file successfully"));
443 } else {
444 QMessageBox::warning(this, tr("Failure"), tr("Failed to import histories from json file"));
445 }
446 return;
447 }
448 if(0 == fi.suffix().compare("csv", Qt::CaseInsensitive)) {
449 if(m_pModelHistory->importFromCSV(filename)) {
450 slotRefresh();
451 QMessageBox::information(this, tr("Success"), tr("Histories import from csv file successfully"));
452 } else {
453 QMessageBox::warning(this, tr("Failure"), tr("Failed to import histories from csv file"));
454 }
455 }
456 }
457}
458
459void CFrmHistory::slotExport()
460{
461 QString filename = QFileDialog::getSaveFileName(
462 this, tr("Export histories"),
463 RabbitCommon::CDir::Instance()->GetDirUserDocument(),
464 tr("JSON (*.json);; CSV (*.csv);; All files (*.*)"));
465
466 if (!filename.isEmpty()) {
467 QFileInfo fi(filename);
468 if(0 == fi.suffix().compare("json", Qt::CaseInsensitive)) {
469 if (m_pModelHistory->exportToJson(filename)) {
470 QMessageBox::information(this, tr("Success"), tr("Histories exported to json file successfully"));
471 } else {
472 QMessageBox::warning(this, tr("Failure"), tr("Failed to export histories to json file"));
473 }
474 return;
475 }
476 if(0 == fi.suffix().compare("csv", Qt::CaseInsensitive)) {
477 if (m_pModelHistory->exportToCSV(filename)) {
478 QMessageBox::information(this, tr("Success"), tr("Histories exported to csv file successfully"));
479 } else {
480 QMessageBox::warning(this, tr("Failure"), tr("Failed to export histories to csv file"));
481 }
482 }
483 }
484}
485
486void CFrmHistory::slotComboxIndexChanged(int index)
487{
488 qDebug(log) << "Change days";
489 QComboBox* pCB = qobject_cast<QComboBox*>(sender());
490 if(!pCB) return;
491 int d = pCB->itemData(index).toInt();
492 QDate curDate = QDate::currentDate();
493 if(m_pDateEnd) {
494 m_pDateEnd->setDate(curDate);
495 }
496 switch(d) {
497 case -1:
498 break;
499 default:
500 if(m_pDateStart)
501 m_pDateStart->setDate(curDate.addDays(-1 * d));
502 break;
503 }
504 slotRefresh();
505}
506
507void CFrmHistory::slotLimit(int v)
508{
509 if(m_pPara)
510 m_pPara->SetDatabaseViewLimit(v);
511 slotRefresh();
512}