1 /*
2 SPDX-FileCopyrightText: 2003 Richard Moore <rich@kde.org>
3 SPDX-FileCopyrightText: 2003 Ian Reinhart Geiser <geiseri@kde.org>
4 SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau <kossebau@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7 */
8
9 #define TRANSLATION_DOMAIN "kuiviewer"
10
11 #include "kuiviewer_part.h"
12
13 // part
14 #include <kuiviewer_part_debug.h>
15 #include "kuiviewer_version.h"
16 // KF
17 #include <KActionCollection>
18 #include <KSelectAction>
19 #include <KConfig>
20 #include <KConfigGroup>
21 #include <KSharedConfig>
22 #include <KLocalizedString>
23 #include <KPluginMetaData>
24 #include <KPluginFactory>
25
26 // Qt
27 #include <QApplication>
28 #include <QClipboard>
29 #include <QFile>
30 #include <QFormBuilder>
31 #include <QStyle>
32 #include <QStyleFactory>
33 #include <QScrollArea>
34 #include <QMdiArea>
35 #include <QMdiSubWindow>
36 #include <QMimeDatabase>
37 #include <QBuffer>
38
39
40 K_PLUGIN_FACTORY_WITH_JSON(KUIViewerPartFactory, "kuiviewer_part.json",
41 registerPlugin<KUIViewerPart>();)
42
KUIViewerPart(QWidget * parentWidget,QObject * parent,const KPluginMetaData & metaData,const QVariantList &)43 KUIViewerPart::KUIViewerPart(QWidget* parentWidget,
44 QObject* parent,
45 const KPluginMetaData& metaData,
46 const QVariantList& /*args*/)
47 : KParts::ReadOnlyPart(parent)
48 , m_subWindow(nullptr)
49 , m_view(nullptr)
50 {
51 setMetaData(metaData);
52
53 // this should be your custom internal widget
54 m_widget = new QMdiArea(parentWidget);
55 m_widget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
56 m_widget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
57
58 // notify the part that this is our internal widget
59 setWidget(m_widget);
60
61 // set our XML-UI resource file
62 setXMLFile(QStringLiteral("kuiviewer_part.rc"));
63
64 m_style = actionCollection()->add<KSelectAction>(QStringLiteral("change_style"));
65 m_style->setText(i18n("Style"));
66 connect(m_style, &KSelectAction::indexTriggered,
67 this, &KUIViewerPart::slotStyle);
68 //m_style->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
69 m_style->setEditable(false);
70
71 m_styleFromConfig = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("currentWidgetStyle", QString());
72
73 const QStringList styles = QStyleFactory::keys();
74 m_style->setItems(QStringList(i18nc("Default style", "Default")) + styles);
75 m_style->setCurrentItem(0);
76
77 // empty or incorrect value means the default value of currentWidgetStyle,
78 // which leads to the Default option.
79 if (!m_styleFromConfig.isEmpty()) {
80 QStringList::ConstIterator it = styles.begin();
81 QStringList::ConstIterator end = styles.end();
82
83 // Skip the default item
84 int idx = 1;
85 for (; it != end; ++it, ++idx) {
86 if ((*it).toLower() == m_styleFromConfig.toLower()) {
87 m_style->setCurrentItem(idx);
88 break;
89 }
90 }
91 }
92
93 m_style->setToolTip(i18n("Set the style used for the view."));
94 m_style->setMenuAccelsEnabled(true);
95
96 m_copy = KStandardAction::copy(this, &KUIViewerPart::slotGrab, actionCollection());
97 m_copy->setText(i18n("Copy as Image"));
98
99 updateActions();
100
101 // Commented out to fix warning (rich)
102 // slot should probably be called saveAs() for consistency with
103 // KParts::ReadWritePart BTW.
104 // KStandardAction::saveAs(this, SLOT(slotSave()), actionCollection());
105 }
106
~KUIViewerPart()107 KUIViewerPart::~KUIViewerPart()
108 {
109 }
110
designerPluginPaths()111 static QStringList designerPluginPaths()
112 {
113 QStringList paths;
114 const QStringList& libraryPaths = QApplication::libraryPaths();
115 for (const auto& path : libraryPaths) {
116 paths.append(path + QLatin1String("/designer"));
117 }
118 return paths;
119 }
120
openFile()121 bool KUIViewerPart::openFile()
122 {
123 // m_file is always local so we can use QFile on it
124 QFile file(localFilePath());
125
126 return loadUiFile(&file);
127 }
128
doOpenStream(const QString & mimeType)129 bool KUIViewerPart::doOpenStream(const QString& mimeType)
130 {
131 auto mime = QMimeDatabase().mimeTypeForName(mimeType);
132 if (!mime.inherits(QStringLiteral("application/x-designer"))) {
133 return false;
134 }
135
136 m_streamedData.clear();
137
138 return true;
139 }
140
doWriteStream(const QByteArray & data)141 bool KUIViewerPart::doWriteStream(const QByteArray& data)
142 {
143 m_streamedData.append(data);
144 return true;
145 }
146
doCloseStream()147 bool KUIViewerPart::doCloseStream()
148 {
149 QBuffer buffer(&m_streamedData);
150
151 const auto success = loadUiFile(&buffer);
152 m_streamedData.clear();
153
154 return success;
155 }
156
loadUiFile(QIODevice * device)157 bool KUIViewerPart::loadUiFile(QIODevice* device)
158 {
159 if (!device->open(QIODevice::ReadOnly|QIODevice::Text)) {
160 qCDebug(KUIVIEWERPART) << "Could not open UI file: " << device->errorString();
161 if (m_previousUrl != url()) {
162 // drop previous view state
163 m_previousScrollPosition = QPoint();
164 m_previousSize = QSize();
165 }
166 return false;
167 }
168
169 if (m_subWindow) {
170 m_widget->removeSubWindow(m_subWindow);
171 delete m_view;
172 delete m_subWindow;
173 m_subWindow = nullptr;
174 }
175
176 QFormBuilder builder;
177 builder.setPluginPath(designerPluginPaths());
178 m_view = builder.load(device, nullptr);
179
180 updateActions();
181
182 if (!m_view) {
183 qCDebug(KUIVIEWERPART) << "Could not load UI file: " << builder.errorString();
184 if (m_previousUrl != url()) {
185 // drop previous view state
186 m_previousScrollPosition = QPoint();
187 m_previousSize = QSize();
188 }
189 return false;
190 }
191
192 // hack ahead:
193 // UI files have a size set for the widget they define. The QMdiSubWindow relies on sizeHint()
194 // during the show event though it seems, to calculate the initial window size, and then discards
195 // the widget size initially set from the builder in the following layout-ruled geometry update.
196 // Enforcing the initial size by manually setting it afterwards to the widget itself seems not possible,
197 // due to the layout government based on window size.
198 // To inject the initial widget size into the initial window geometry, as hack the min and max sizes are
199 // temporarily set to the wanted size and, once the window is shown, reset to their initial values.
200 const QSize widgetSize = m_view->size();
201 const QSize origWidgetMinimumSize = m_view->minimumSize();
202 const QSize origWidgetMaximumSize = m_view->maximumSize();
203 restyleView(m_style->currentText());
204 m_view->setMinimumSize(widgetSize);
205 m_view->setMaximumSize(widgetSize);
206
207 const Qt::WindowFlags windowFlags(Qt::SubWindow|Qt::CustomizeWindowHint|Qt::WindowTitleHint);
208 m_subWindow = m_widget->addSubWindow(m_view, windowFlags);
209 // prevent focus stealing by adding the window in disabled state
210 m_subWindow->setEnabled(false);
211 m_subWindow->show();
212 // and restore minimum size
213 m_view->setMinimumSize(origWidgetMinimumSize);
214 m_view->setMaximumSize(origWidgetMaximumSize);
215
216 m_widget->setActiveSubWindow(m_subWindow);
217 m_subWindow->setEnabled(true);
218
219 // restore view state if reload
220 if (url() == m_previousUrl) {
221 qCDebug(KUIVIEWERPART) << "Restoring previous view state";
222 m_subWindow->move(m_previousScrollPosition);
223 if (m_previousSize.isValid()) {
224 m_subWindow->resize(m_previousSize);
225 }
226 }
227
228 return true;
229 }
230
closeUrl()231 bool KUIViewerPart::closeUrl()
232 {
233 // store view state if file could be loaded
234 // otherwise keep old in case same url will get reloaded again and then successfully
235 if (m_subWindow) {
236 m_previousScrollPosition = m_subWindow->pos();
237 m_previousSize = m_subWindow->size();
238 }
239 // store last used url
240 const auto activeUrl = url();
241 if (activeUrl.isValid()) {
242 m_previousUrl = activeUrl;
243 }
244
245 m_streamedData.clear();
246
247 return ReadOnlyPart::closeUrl();
248 }
249
updateActions()250 void KUIViewerPart::updateActions()
251 {
252 const bool hasView = !m_view.isNull();
253
254 m_style->setEnabled(hasView);
255 m_copy->setEnabled(hasView);
256 }
257
restyleView(const QString & styleName)258 void KUIViewerPart::restyleView(const QString& styleName)
259 {
260 QStyle* style = QStyleFactory::create(styleName);
261
262 m_view->setStyle(style);
263
264 const QList<QWidget*> childWidgets = m_view->findChildren<QWidget*>();
265 for (auto child : childWidgets) {
266 child->setStyle(style);
267 }
268 }
269
setWidgetSize(const QSize & size)270 void KUIViewerPart::setWidgetSize(const QSize& size)
271 {
272 if (m_view.isNull()) {
273 return;
274 }
275
276 // hack: enforce widget size by setting min/max sizes to wanted size
277 // and then have layout update the complete window
278 const QSize origWidgetMinimumSize = m_view->minimumSize();
279 const QSize origWidgetMaximumSize = m_view->maximumSize();
280 m_view->setMinimumSize(size);
281 m_view->setMaximumSize(size);
282 m_subWindow->updateGeometry();
283 // restore
284 m_view->setMinimumSize(origWidgetMinimumSize);
285 m_view->setMaximumSize(origWidgetMaximumSize);
286 }
287
renderWidgetAsPixmap() const288 QPixmap KUIViewerPart::renderWidgetAsPixmap() const
289 {
290 if (m_view.isNull()) {
291 return QPixmap();
292 }
293
294 return m_view->grab();
295 }
296
slotStyle(int)297 void KUIViewerPart::slotStyle(int)
298 {
299 if (m_view.isNull()) {
300 updateActions();
301 return;
302 }
303
304 m_view->hide();
305
306 const QString styleName = m_style->currentText();
307 qCDebug(KUIVIEWERPART) << "Style selected:" << styleName;
308 restyleView(styleName);
309
310 m_view->show();
311
312 /* the style changed, update the configuration */
313 if (m_styleFromConfig != styleName) {
314 KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
315 KConfigGroup cg(cfg, "General");
316 if (m_style->currentItem() > 0) {
317 /* A style different from the default */
318 cg.writeEntry("currentWidgetStyle", styleName);
319 } else {
320 /* default style: remove the entry */
321 cg.deleteEntry("currentWidgetStyle");
322 }
323 cfg->sync();
324 }
325 }
326
slotGrab()327 void KUIViewerPart::slotGrab()
328 {
329 if (m_view.isNull()) {
330 updateActions();
331 return;
332 }
333
334 const QPixmap pixmap = m_view->grab();
335 QApplication::clipboard()->setPixmap(pixmap);
336 }
337
338 #include "kuiviewer_part.moc"
339