1 /*
2 This file is part of Lokalize
3
4 SPDX-FileCopyrightText: 2007-2009 Nick Shaforostoff <shafff@ukr.net>
5 SPDX-FileCopyrightText: 2018-2019 Simon Depiets <sdepiets@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
8 */
9
10 #include "cataloglistview.h"
11
12 #include "lokalize_debug.h"
13
14 #include "catalogmodel.h"
15 #include "catalog.h"
16 #include "project.h"
17 #include "prefs.h"
18 #include "headerviewmenu.h"
19
20 #include <QLineEdit>
21 #include <QTime>
22 #include <QTreeView>
23 #include <QHeaderView>
24 #include <QModelIndex>
25 #include <QToolButton>
26 #include <QVBoxLayout>
27 #include <QAction>
28 #include <QMenu>
29 #include <QShortcut>
30 #include <QKeyEvent>
31
32 #include <KLocalizedString>
33 #include <KConfigGroup>
34
35 class CatalogTreeView: public QTreeView
36 {
37 public:
CatalogTreeView(QWidget * parent)38 CatalogTreeView(QWidget * parent)
39 : QTreeView(parent) {}
~CatalogTreeView()40 ~CatalogTreeView() {}
41
42 protected:
keyReleaseEvent(QKeyEvent * e)43 void keyReleaseEvent(QKeyEvent *e) override
44 {
45 if (e->key() == Qt::Key_Return && currentIndex().isValid()) {
46 Q_EMIT clicked(currentIndex());
47 e->accept();
48 } else {
49 QTreeView::keyReleaseEvent(e);
50 }
51 }
52
53 };
54
55
CatalogView(QWidget * parent,Catalog * catalog)56 CatalogView::CatalogView(QWidget* parent, Catalog* catalog)
57 : QDockWidget(i18nc("@title:window aka Message Tree", "Translation Units"), parent)
58 , m_browser(new CatalogTreeView(this))
59 , m_lineEdit(new QLineEdit(this))
60 , m_model(new CatalogTreeModel(this, catalog))
61 , m_proxyModel(new CatalogTreeFilterModel(this))
62 {
63 setObjectName(QStringLiteral("catalogTreeView"));
64
65 QWidget* w = new QWidget(this);
66 QVBoxLayout* layout = new QVBoxLayout(w);
67 layout->setContentsMargins(0, 0, 0, 0);
68 QHBoxLayout* l = new QHBoxLayout;
69 l->setContentsMargins(0, 0, 0, 0);
70 l->setSpacing(0);
71 layout->addLayout(l);
72
73 m_lineEdit->setClearButtonEnabled(true);
74 m_lineEdit->setPlaceholderText(i18n("Quick search..."));
75 m_lineEdit->setToolTip(i18nc("@info:tooltip", "Activated by Ctrl+L.") + ' ' + i18nc("@info:tooltip", "Accepts regular expressions"));
76 connect(m_lineEdit, &QLineEdit::textChanged, this, &CatalogView::setFilterRegExp, Qt::QueuedConnection);
77 // QShortcut* ctrlEsc=new QShortcut(QKeySequence(Qt::META+Qt::Key_Escape),this,SLOT(reset()),0,Qt::WidgetWithChildrenShortcut);
78 QShortcut* esc = new QShortcut(QKeySequence(Qt::Key_Escape), this, nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
79 connect(esc, &QShortcut::activated, this, &CatalogView::escaped);
80
81
82 QToolButton* btn = new QToolButton(w);
83 btn->setPopupMode(QToolButton::InstantPopup);
84 btn->setText(i18n("options"));
85 //btn->setArrowType(Qt::DownArrow);
86 btn->setMenu(new QMenu(this));
87 m_filterOptionsMenu = btn->menu();
88 connect(m_filterOptionsMenu, &QMenu::aboutToShow, this, &CatalogView::fillFilterOptionsMenu);
89 connect(m_filterOptionsMenu, &QMenu::triggered, this, &CatalogView::filterOptionToggled);
90
91 l->addWidget(m_lineEdit);
92 l->addWidget(btn);
93 layout->addWidget(m_browser);
94
95
96 setTabOrder(m_lineEdit, btn);
97 setTabOrder(btn, m_browser);
98 setFocusProxy(m_lineEdit);
99
100 setWidget(w);
101
102 connect(m_browser, &CatalogTreeView::clicked, this, &CatalogView::slotItemActivated);
103 m_browser->setRootIsDecorated(false);
104 m_browser->setAllColumnsShowFocus(true);
105 m_browser->setAlternatingRowColors(true);
106 m_browser->viewport()->setBackgroundRole(QPalette::Window);
107 #ifdef Q_OS_DARWIN
108 QPalette p;
109 p.setColor(QPalette::AlternateBase, p.color(QPalette::Window).darker(110));
110 p.setColor(QPalette::Highlight, p.color(QPalette::Window).darker(150));
111 m_browser->setPalette(p);
112 #endif
113
114 m_proxyModel->setSourceModel(m_model);
115 m_browser->setModel(m_proxyModel);
116 m_browser->setColumnWidth(0, m_browser->columnWidth(0) / 3);
117 m_browser->setSortingEnabled(true);
118 m_browser->sortByColumn(0, Qt::AscendingOrder);
119 m_browser->setWordWrap(false);
120 m_browser->setUniformRowHeights(true);
121 m_browser->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
122
123 new HeaderViewMenuHandler(m_browser->header());
124 m_browser->header()->restoreState(readUiState("CatalogTreeViewState"));
125 }
126
~CatalogView()127 CatalogView::~CatalogView()
128 {
129 writeUiState("CatalogTreeViewState", m_browser->header()->saveState());
130 }
131
setFocus()132 void CatalogView::setFocus()
133 {
134 QDockWidget::setFocus();
135 m_lineEdit->selectAll();
136 }
137
slotNewEntryDisplayed(const DocPosition & pos)138 void CatalogView::slotNewEntryDisplayed(const DocPosition& pos)
139 {
140 QModelIndex item = m_proxyModel->mapFromSource(m_model->index(pos.entry, 0));
141 m_browser->setCurrentIndex(item);
142 m_browser->scrollTo(item/*,QAbstractItemView::PositionAtCenter*/);
143 m_lastKnownDocPosition = pos.entry;
144 }
145
setFilterRegExp()146 void CatalogView::setFilterRegExp()
147 {
148 QString expr = m_lineEdit->text();
149 if (m_proxyModel->filterRegExp().pattern() != expr)
150 m_proxyModel->setFilterRegExp(m_proxyModel->filterOptions()&CatalogTreeFilterModel::IgnoreAccel ? expr.remove(Project::instance()->accel()) : expr);
151 refreshCurrentIndex();
152 }
153
refreshCurrentIndex()154 void CatalogView::refreshCurrentIndex()
155 {
156 QModelIndex newPositionOfSelectedItem = m_proxyModel->mapFromSource(m_model->index(m_lastKnownDocPosition, 0));
157 m_browser->setCurrentIndex(newPositionOfSelectedItem);
158 m_browser->scrollTo(newPositionOfSelectedItem);
159 }
160
slotItemActivated(const QModelIndex & idx)161 void CatalogView::slotItemActivated(const QModelIndex& idx)
162 {
163 Q_EMIT gotoEntry(DocPosition(m_proxyModel->mapToSource(idx).row()), 0);
164 }
165
filterOptionToggled(QAction * action)166 void CatalogView::filterOptionToggled(QAction* action)
167 {
168 if (action->data().isNull())
169 return;
170
171 int opt = action->data().toInt();
172 if (opt > 0)
173 m_proxyModel->setFilterOptions(m_proxyModel->filterOptions()^opt);
174 else {
175 if (opt != -1) opt = -opt - 2;
176 m_proxyModel->setFilterKeyColumn(opt);
177 }
178 m_filterOptionsMenu->clear();
179 refreshCurrentIndex();
180 }
fillFilterOptionsMenu()181 void CatalogView::fillFilterOptionsMenu()
182 {
183 m_filterOptionsMenu->clear();
184
185 if (m_proxyModel->individualRejectFilterEnabled())
186 m_filterOptionsMenu->addAction(i18n("Reset individual filter"), this, SLOT(setEntriesFilteredOut()));
187
188
189 bool extStates = m_model->catalog()->capabilities()&ExtendedStates;
190
191 const char* const basicTitles[] = {
192 I18N_NOOP("Case insensitive"),
193 I18N_NOOP("Ignore accelerator marks"),
194 I18N_NOOP("Ready"),
195 I18N_NOOP("Non-ready"),
196 I18N_NOOP("Non-empty"),
197 I18N_NOOP("Empty"),
198 I18N_NOOP("Changed since file open"),
199 I18N_NOOP("Unchanged since file open"),
200 I18N_NOOP("Same in sync file"),
201 I18N_NOOP("Different in sync file"),
202 I18N_NOOP("Not in sync file"),
203 I18N_NOOP("Plural"),
204 I18N_NOOP("Non-plural"),
205 };
206 const char* const* extTitles = Catalog::states();
207 const char* const* alltitles[2] = {basicTitles, extTitles};
208
209 QMenu* basicMenu = m_filterOptionsMenu->addMenu(i18nc("@title:inmenu", "Basic"));
210 QMenu* extMenu = extStates ? m_filterOptionsMenu->addMenu(i18nc("@title:inmenu", "States")) : nullptr;
211 QMenu* allmenus[2] = {basicMenu, extMenu};
212 QMenu* columnsMenu = m_filterOptionsMenu->addMenu(i18nc("@title:inmenu", "Searchable column"));
213
214 QActionGroup* columnsMenuGroup = new QActionGroup(columnsMenu);
215 QAction* txt;
216 txt = m_filterOptionsMenu->addAction(i18nc("@title:inmenu", "Resort and refilter on content change"), m_proxyModel, &CatalogTreeFilterModel::setDynamicSortFilter);
217 txt->setCheckable(true);
218 txt->setChecked(m_proxyModel->dynamicSortFilter());
219
220 for (int i = 0; (1 << i) < CatalogTreeFilterModel::MaxOption; ++i) {
221 bool ext = (1 << i) >= CatalogTreeFilterModel::New;
222 if (!extStates && ext) break;
223 txt = allmenus[ext]->addAction(i18n(alltitles[ext][i - ext * FIRSTSTATEPOSITION]));
224 txt->setData(1 << i);
225 txt->setCheckable(true);
226 txt->setChecked(m_proxyModel->filterOptions() & (1 << i));
227 if ((1 << i) == CatalogTreeFilterModel::IgnoreAccel)
228 basicMenu->addSeparator();
229 }
230 if (!extStates)
231 m_filterOptionsMenu->addSeparator();
232 for (int i = -1; i < CatalogTreeModel::DisplayedColumnCount - 1; ++i) {
233 txt = columnsMenu->addAction((i == -1) ? i18nc("@item:inmenu all columns", "All") :
234 m_model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString());
235 txt->setData(-i - 2);
236 txt->setCheckable(true);
237 txt->setChecked(m_proxyModel->filterKeyColumn() == i);
238 txt->setActionGroup(columnsMenuGroup);
239 }
240 refreshCurrentIndex();
241 }
242
reset()243 void CatalogView::reset()
244 {
245 m_proxyModel->setFilterKeyColumn(-1);
246 m_proxyModel->setFilterOptions(CatalogTreeFilterModel::AllStates);
247 m_lineEdit->clear();
248 refreshCurrentIndex();
249 //Q_EMIT gotoEntry(DocPosition(m_proxyModel->mapToSource(m_browser->currentIndex()).row()),0);
250 slotItemActivated(m_browser->currentIndex());
251 }
252
setMergeCatalogPointer(MergeCatalog * pointer)253 void CatalogView::setMergeCatalogPointer(MergeCatalog* pointer)
254 {
255 m_proxyModel->setMergeCatalogPointer(pointer);
256 }
257
siblingEntryNumber(int step)258 int CatalogView::siblingEntryNumber(int step)
259 {
260 QModelIndex item = m_browser->currentIndex();
261 int lastRow = m_proxyModel->rowCount() - 1;
262 if (!item.isValid()) {
263 if (lastRow == -1)
264 return -1;
265 item = m_proxyModel->index((step == 1) ? 0 : lastRow, 0);
266 m_browser->setCurrentIndex(item);
267 } else {
268 if (item.row() == ((step == -1) ? 0 : lastRow))
269 return -1;
270 item = item.sibling(item.row() + step, 0);
271 }
272 return m_proxyModel->mapToSource(item).row();
273 }
274
nextEntryNumber()275 int CatalogView::nextEntryNumber()
276 {
277 return siblingEntryNumber(1);
278 }
279
prevEntryNumber()280 int CatalogView::prevEntryNumber()
281 {
282 return siblingEntryNumber(-1);
283 }
284
edgeEntry(CatalogTreeFilterModel * m_proxyModel,int row)285 static int edgeEntry(CatalogTreeFilterModel* m_proxyModel, int row)
286 {
287 if (!m_proxyModel->rowCount())
288 return -1;
289
290 return m_proxyModel->mapToSource(m_proxyModel->index(row, 0)).row();
291 }
292
firstEntryNumber()293 int CatalogView::firstEntryNumber()
294 {
295 return edgeEntry(m_proxyModel, 0);
296 }
297
lastEntryNumber()298 int CatalogView::lastEntryNumber()
299 {
300 return edgeEntry(m_proxyModel, m_proxyModel->rowCount() - 1);
301 }
302
303
setEntryFilteredOut(int entry,bool filteredOut)304 void CatalogView::setEntryFilteredOut(int entry, bool filteredOut)
305 {
306 m_proxyModel->setEntryFilteredOut(entry, filteredOut);
307 refreshCurrentIndex();
308 }
309
setEntriesFilteredOut(bool filteredOut)310 void CatalogView::setEntriesFilteredOut(bool filteredOut)
311 {
312 show();
313 m_proxyModel->setEntriesFilteredOut(filteredOut);
314 refreshCurrentIndex();
315 }
316
317