1 /***************************************************************************
2 * Copyright (C) 2005-2006 David Saxton <david@bluehaze.org> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
9
10 #include "docmanager.h"
11 #include "document.h"
12 #include "itemview.h"
13 #include "ktechlab.h"
14 #include "view.h"
15 #include "viewcontainer.h"
16
17 #include <KLocalizedString>
18 #include <KConfigGroup>
19
20 #include <QDebug>
21 #include <QHBoxLayout>
22 #include <QTabWidget>
23 #include <QPushButton>
24 //#include <qobjectlist.h>
25
26
27 //BEGIN class ViewContainer
ViewContainer(const QString & caption,QWidget * parent)28 ViewContainer::ViewContainer( const QString & caption, QWidget * parent )
29 : QWidget( parent ? parent : KTechlab::self()->tabWidget() )
30 {
31 b_deleted = false;
32 connect( KTechlab::self(), SIGNAL(needUpdateCaptions()), this, SLOT(updateCaption()) );
33
34 QHBoxLayout *layout = new QHBoxLayout(this);
35 m_baseViewArea = new ViewArea( this, this, 0, false, "viewarea_0" );
36 connect( m_baseViewArea, SIGNAL(destroyed(QObject* )), this, SLOT(baseViewAreaDestroyed(QObject* )) );
37
38 layout->addWidget(m_baseViewArea);
39
40 m_activeViewArea = 0;
41 setFocusProxy( m_baseViewArea );
42
43 if ( !parent )
44 {
45 KTechlab::self()->tabWidget()->addTab( this, caption );
46 QTabWidget * tabWidget = KTechlab::self()->tabWidget();
47 tabWidget->setCurrentIndex( tabWidget->indexOf(this) );
48 }
49
50 show();
51 }
52
53
~ViewContainer()54 ViewContainer::~ViewContainer()
55 {
56 b_deleted = true;
57 }
58
59
setActiveViewArea(uint id)60 void ViewContainer::setActiveViewArea( uint id )
61 {
62 if ( m_activeViewArea == int(id) )
63 return;
64
65 m_activeViewArea = id;
66 View * newView = view(id);
67 setFocusProxy( newView );
68
69 if ( newView )
70 {
71 setWindowTitle( newView->windowTitle() );
72
73 if ( !DocManager::self()->getFocusedView() && newView->isVisible() )
74 newView->setFocus();
75 }
76 }
77
78
view(uint id) const79 View *ViewContainer::view( uint id ) const
80 {
81 ViewArea *va = viewArea(id);
82 if (!va)
83 return nullptr;
84
85 // We do not want a recursive search as ViewAreas also hold other ViewAreas
86 //QObjectList l = va->queryList( "View", 0, false, false ); // 2018.12.02
87 QList<View*> l = va->findChildren<View*>();
88 View *view = nullptr;
89 if ( !l.isEmpty() )
90 view = dynamic_cast<View*>(l.first());
91 //delete l;
92
93 return view;
94 }
95
96
viewArea(uint id) const97 ViewArea *ViewContainer::viewArea( uint id ) const
98 {
99 if ( !m_viewAreaMap.contains(id) )
100 return nullptr;
101
102 return m_viewAreaMap[id];
103 }
104
105
closeViewContainer()106 bool ViewContainer::closeViewContainer()
107 {
108 bool didClose = true;
109 while ( didClose && !m_viewAreaMap.isEmpty() )
110 {
111 didClose = closeViewArea( m_viewAreaMap.begin().key() );
112 }
113
114 return m_viewAreaMap.isEmpty();
115 }
116
117
closeViewArea(uint id)118 bool ViewContainer::closeViewArea( uint id )
119 {
120 ViewArea *va = viewArea(id);
121 if ( !va )
122 return true;
123
124 bool doClose = false;
125 View *v = view(id);
126 if ( v && v->document() )
127 {
128 doClose = v->document()->numberOfViews() > 1;
129 if (!doClose)
130 doClose = v->document()->fileClose();
131 }
132 else
133 doClose = true;
134
135 if (!doClose)
136 return false;
137
138 m_viewAreaMap.remove(id);
139 va->deleteLater();
140
141 if ( m_activeViewArea == int(id) )
142 {
143 m_activeViewArea = -1;
144 findActiveViewArea();
145 }
146
147 return true;
148 }
149
150
createViewArea(int relativeViewArea,ViewArea::Position position,bool showOpenButton)151 int ViewContainer::createViewArea( int relativeViewArea, ViewArea::Position position, bool showOpenButton )
152 {
153 if ( relativeViewArea == -1 )
154 relativeViewArea = activeViewArea();
155
156 ViewArea *relative = viewArea(relativeViewArea);
157 if (!relative)
158 {
159 qCritical() << Q_FUNC_INFO << "Could not find relative view area" << endl;
160 return -1;
161 }
162
163 uint id = uniqueNewId();
164 // setActiveViewArea(id);
165
166 ViewArea *viewArea = relative->createViewArea( position, id, showOpenButton );
167 // ViewArea *viewArea = new ViewArea( m_splitter, id, (const char*)("viewarea_"+QString::number(id)) );
168 viewArea->show(); // remove?
169
170 return id;
171 }
172
173
setViewAreaId(ViewArea * viewArea,uint id)174 void ViewContainer::setViewAreaId( ViewArea *viewArea, uint id )
175 {
176 m_viewAreaMap[id] = viewArea;
177 m_usedIDs.append(id);
178 }
179
180
setViewAreaRemoved(uint id)181 void ViewContainer::setViewAreaRemoved( uint id )
182 {
183 if (b_deleted)
184 return;
185
186 ViewAreaMap::iterator it = m_viewAreaMap.find(id);
187 if ( it == m_viewAreaMap.end() )
188 return;
189
190 m_viewAreaMap.erase(it);
191
192 if ( m_activeViewArea == int(id) )
193 findActiveViewArea();
194 }
195
196
findActiveViewArea()197 void ViewContainer::findActiveViewArea()
198 {
199 if ( m_viewAreaMap.isEmpty() )
200 return;
201
202 setActiveViewArea( (--m_viewAreaMap.end()).key() );
203 }
204
205
baseViewAreaDestroyed(QObject * obj)206 void ViewContainer::baseViewAreaDestroyed( QObject *obj )
207 {
208 if (!obj)
209 return;
210
211 if (!b_deleted)
212 {
213 b_deleted = true;
214 close();
215 deleteLater();
216 }
217 }
218
219
canSaveUsefulStateInfo() const220 bool ViewContainer::canSaveUsefulStateInfo() const
221 {
222 return m_baseViewArea && m_baseViewArea->canSaveUsefulStateInfo();
223 }
224
225
saveState(KConfigGroup * config)226 void ViewContainer::saveState( KConfigGroup *config )
227 {
228 if (!m_baseViewArea)
229 return;
230
231 config->writeEntry( "BaseViewArea", m_baseViewArea->id() );
232 m_baseViewArea->saveState(config);
233 }
234
235
restoreState(KConfigGroup * config,const QString & groupName)236 void ViewContainer::restoreState( KConfigGroup* config, const QString& groupName )
237 {
238 //config->setGroup(groupName);
239 int baseAreaId = config->readEntry("BaseViewArea", 0);
240 m_baseViewArea->restoreState( config, baseAreaId, groupName );
241 }
242
243
uniqueParentId()244 int ViewContainer::uniqueParentId()
245 {
246 int lowest = -1;
247 const IntList::iterator end = m_usedIDs.end();
248 for ( IntList::iterator it = m_usedIDs.begin(); it != end; ++it )
249 {
250 if ( *it < lowest )
251 lowest = *it;
252 }
253 int newId = lowest-1;
254 m_usedIDs.append(newId);
255 return newId;
256 }
257
258
uniqueNewId()259 int ViewContainer::uniqueNewId()
260 {
261 int highest = 0;
262 const IntList::iterator end = m_usedIDs.end();
263 for ( IntList::iterator it = m_usedIDs.begin(); it != end; ++it )
264 {
265 if ( *it > highest )
266 highest = *it;
267 }
268 int newId = highest+1;
269 m_usedIDs.append(newId);
270 return newId;
271 }
272
273
setIdUsed(int id)274 void ViewContainer::setIdUsed( int id )
275 {
276 m_usedIDs.append(id);
277 }
278
279
updateCaption()280 void ViewContainer::updateCaption()
281 {
282 QString caption;
283
284 if ( !activeView() || !activeView()->document() )
285 caption = i18n("(empty)");
286
287 else
288 {
289 Document * doc = activeView()->document();
290 caption = doc->url().isEmpty() ? doc->caption() : doc->url().fileName();
291 if ( viewCount() > 1 )
292 caption += " ...";
293 }
294
295 setWindowTitle(caption);
296 //KTechlab::self()->tabWidget()->setTabLabel( this, caption ); // 2018.12.02
297 KTechlab::self()->tabWidget()->setTabText(
298 KTechlab::self()->tabWidget()->indexOf(this),
299 caption );
300 }
301 //END class ViewContainer
302
303
304 //BEGIN class ViewArea
ViewArea(QWidget * parent,ViewContainer * viewContainer,int id,bool showOpenButton,const char * name)305 ViewArea::ViewArea( QWidget *parent, ViewContainer *viewContainer, int id, bool showOpenButton, const char *name )
306 : QSplitter( parent /*, name */ )
307 {
308 setObjectName(name);
309 p_viewContainer = viewContainer;
310 m_id = id;
311 p_view = nullptr;
312 p_viewArea1 = nullptr;
313 p_viewArea2 = nullptr;
314
315 if (id >= 0)
316 p_viewContainer->setViewAreaId( this, uint(id) );
317
318 p_viewContainer->setIdUsed(id);
319
320 m_pEmptyViewArea = nullptr;
321 if ( showOpenButton )
322 m_pEmptyViewArea = new EmptyViewArea( this );
323 }
324
325
~ViewArea()326 ViewArea::~ViewArea()
327 {
328 if ( m_id >= 0 )
329 p_viewContainer->setViewAreaRemoved( uint(m_id) );
330 }
331
332
createViewArea(Position position,uint id,bool showOpenButton)333 ViewArea *ViewArea::createViewArea( Position position, uint id, bool showOpenButton )
334 {
335 if (p_viewArea1 || p_viewArea2)
336 {
337 qCritical() << Q_FUNC_INFO << "Attempting to create ViewArea when already containing ViewAreas!" << endl;
338 return nullptr;
339 }
340 if (!p_view)
341 {
342 qCritical() << Q_FUNC_INFO << "We don't have a view yet, so creating a new ViewArea is redundant" << endl;
343 return nullptr;
344 }
345
346 setOrientation( ( position == Right ) ? Qt::Horizontal : Qt::Vertical );
347
348 p_viewArea1 = new ViewArea( this, p_viewContainer, m_id, false,
349 ("viewarea_"+QString::number(m_id)).toLatin1().data() );
350 p_viewArea2 = new ViewArea( this, p_viewContainer, id, showOpenButton,
351 ("viewarea_"+QString::number(id)).toLatin1().data() );
352
353 connect( p_viewArea1, SIGNAL(destroyed(QObject* )), this, SLOT(viewAreaDestroyed(QObject* )) );
354 connect( p_viewArea2, SIGNAL(destroyed(QObject* )), this, SLOT(viewAreaDestroyed(QObject* )) );
355
356 p_view->clearFocus();
357 //p_view->reparent( p_viewArea1, QPoint(), true ); // 2018.12.02
358 p_view->setParent( p_viewArea1 );
359 p_view->move(QPoint());
360 p_view->show();
361 p_viewArea1->setView(p_view);
362 setView( nullptr );
363
364 m_id = p_viewContainer->uniqueParentId();
365
366 QList<int> splitPos;
367 int pos = ((orientation() == Qt::Horizontal) ? width()/2 : height()/2);
368 splitPos << pos << pos;
369 setSizes(splitPos);
370
371 p_viewArea1->show();
372 p_viewArea2->show();
373 return p_viewArea2;
374 }
375
376
viewAreaDestroyed(QObject * obj)377 void ViewArea::viewAreaDestroyed( QObject *obj )
378 {
379 ViewArea *viewArea = static_cast<ViewArea*>(obj);
380
381 if ( viewArea == p_viewArea1 )
382 p_viewArea1 = nullptr;
383
384 if ( viewArea == p_viewArea2 )
385 p_viewArea2 = nullptr;
386
387 if ( !p_viewArea1 && !p_viewArea2 )
388 deleteLater();
389 }
390
391
setView(View * view)392 void ViewArea::setView( View *view )
393 {
394 if ( !view )
395 {
396 p_view = nullptr;
397 setFocusProxy( nullptr );
398 return;
399 }
400
401 delete m_pEmptyViewArea;
402
403 if ( p_view )
404 {
405 qCritical() << Q_FUNC_INFO << "Attempting to set already contained view!" << endl;
406 return;
407 }
408
409 p_view = view;
410
411 // qDebug() << Q_FUNC_INFO << "p_view->isFocusEnabled()="<<p_view->isFocusEnabled()<<" p_view->isHidden()="<<p_view->isHidden()<<endl;
412
413 connect( view, SIGNAL(destroyed()), this, SLOT(viewDestroyed()) );
414 bool hadFocus = hasFocus();
415 setFocusProxy( p_view );
416 if ( hadFocus && !p_view->isHidden() )
417 p_view->setFocus();
418
419 // The ViewContainer by default has a view area as its focus proxy.
420 // This is because there is no view when it is constructed. So give
421 // it our view as the focus proxy if it doesn't have one.
422 if ( !dynamic_cast<View*>(p_viewContainer->focusProxy()) )
423 p_viewContainer->setFocusProxy( p_view );
424 }
425
426
viewDestroyed()427 void ViewArea::viewDestroyed()
428 {
429 if ( !p_view && !p_viewArea1 && !p_viewArea2 )
430 deleteLater();
431 }
432
433
canSaveUsefulStateInfo() const434 bool ViewArea::canSaveUsefulStateInfo() const
435 {
436 if ( p_viewArea1 && p_viewArea1->canSaveUsefulStateInfo() )
437 return true;
438
439 if ( p_viewArea2 && p_viewArea2->canSaveUsefulStateInfo() )
440 return true;
441
442 if ( p_view && p_view->document() && !p_view->document()->url().isEmpty() )
443 return true;
444
445 return false;
446 }
447
448
saveState(KConfigGroup * config)449 void ViewArea::saveState( KConfigGroup* config )
450 {
451 bool va1Ok = p_viewArea1 && p_viewArea1->canSaveUsefulStateInfo();
452 bool va2Ok = p_viewArea2 && p_viewArea2->canSaveUsefulStateInfo();
453
454 if ( va1Ok || va2Ok )
455 {
456 config->writeEntry( orientationKey(m_id), (orientation() == Qt::Horizontal) ? "LeftRight" : "TopBottom" );
457
458 QList<int> contains;
459 if (va1Ok)
460 contains << p_viewArea1->id();
461 if (va2Ok)
462 contains << p_viewArea2->id();
463
464 config->writeEntry( containsKey(m_id), contains );
465 if (va1Ok)
466 p_viewArea1->saveState(config);
467 if (va2Ok)
468 p_viewArea2->saveState(config);
469 }
470 else if ( p_view && !p_view->document()->url().isEmpty() )
471 {
472 config->writePathEntry( fileKey(m_id), p_view->document()->url().toDisplayString(QUrl::PreferLocalFile) );
473 }
474 }
475
476
restoreState(KConfigGroup * config,int id,const QString & groupName)477 void ViewArea::restoreState( KConfigGroup* config, int id, const QString& groupName )
478 {
479 if (!config)
480 return;
481
482 if ( id != m_id )
483 {
484 if ( m_id >= 0 )
485 p_viewContainer->setViewAreaRemoved( uint(m_id) );
486
487 m_id = id;
488
489 if ( m_id >= 0 )
490 p_viewContainer->setViewAreaId( this, uint(m_id) );
491
492 p_viewContainer->setIdUsed(id);
493 }
494
495 //config->setGroup(groupName);
496 if ( config->hasKey( orientationKey(id) ) )
497 {
498 QString orientation = config->readEntry( orientationKey(m_id) );
499 setOrientation( (orientation == "LeftRight") ? Qt::Horizontal : Qt::Vertical );
500 }
501
502 //config->setGroup(groupName);
503 if ( config->hasKey( containsKey(m_id) ) )
504 {
505 typedef QList<int> IntList;
506 IntList contains = config->readEntry( containsKey(m_id), IntList());
507
508 if ( contains.isEmpty() || contains.size() > 2 )
509 qCritical() << Q_FUNC_INFO << "Contained list has wrong size of " << contains.size() << endl;
510
511 else
512 {
513 if ( contains.size() >= 1 )
514 {
515 int viewArea1Id = contains[0];
516 p_viewArea1 = new ViewArea( this, p_viewContainer, viewArea1Id, false,
517 ("viewarea_"+QString::number(viewArea1Id)).toLatin1().data() );
518 connect( p_viewArea1, SIGNAL(destroyed(QObject* )), this, SLOT(viewAreaDestroyed(QObject* )) );
519 p_viewArea1->restoreState( config, viewArea1Id, groupName );
520 p_viewArea1->show();
521 }
522
523 if ( contains.size() >= 2 )
524 {
525 int viewArea2Id = contains[1];
526 p_viewArea2 = new ViewArea( this, p_viewContainer, viewArea2Id, false,
527 ("viewarea_"+QString::number(viewArea2Id)).toLatin1().data() );
528 connect( p_viewArea2, SIGNAL(destroyed(QObject* )), this, SLOT(viewAreaDestroyed(QObject* )) );
529 p_viewArea2->restoreState( config, viewArea2Id, groupName );
530 p_viewArea2->show();
531 }
532 }
533 }
534
535 //config->setGroup(groupName);
536 if ( config->hasKey( fileKey(m_id) ) )
537 {
538 const QUrl url = QUrl::fromUserInput(config->readPathEntry(fileKey(m_id), QString()));
539 bool openedOk = DocManager::self()->openURL( url, this );
540 if (!openedOk)
541 deleteLater();
542 }
543 }
544
fileKey(int id)545 QString ViewArea::fileKey( int id )
546 {
547 return QString("ViewArea ") + QString::number(id) + QString(" file");
548 }
containsKey(int id)549 QString ViewArea::containsKey( int id )
550 {
551 return QString("ViewArea ") + QString::number(id) + QString(" contains");
552 }
orientationKey(int id)553 QString ViewArea::orientationKey( int id )
554 {
555 return QString("ViewArea ") + QString::number(id) + QString(" orientation");
556 }
557 //END class ViewArea
558
559
560
561 //BEGIN class EmptyViewArea
EmptyViewArea(ViewArea * parent)562 EmptyViewArea::EmptyViewArea( ViewArea * parent )
563 : QWidget( parent )
564 {
565 m_pViewArea = parent;
566
567 QGridLayout * layout = new QGridLayout( this /*, 5, 3, 0, 6 */ );
568 layout->setMargin(0);
569 layout->setSpacing(6);
570
571 layout->setRowStretch( 0, 20 );
572 layout->setRowStretch( 2, 1 );
573 layout->setRowStretch( 4, 20 );
574
575 layout->setColumnStretch( 0, 1 );
576 layout->setColumnStretch( 2, 1 );
577
578 QPushButton * newDocButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")),
579 i18n("Open Document"), this );
580 layout->addWidget( newDocButton, 1, 1 );
581 connect( newDocButton, SIGNAL(clicked()), this, SLOT(openDocument()) );
582
583 QPushButton * cancelButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-cancel")),
584 i18n("Cancel"), this );
585 layout->addWidget( cancelButton, 3, 1 );
586 connect( cancelButton, SIGNAL(clicked()), m_pViewArea, SLOT(deleteLater()) );
587 }
588
589
~EmptyViewArea()590 EmptyViewArea::~EmptyViewArea()
591 {
592 }
593
594
openDocument()595 void EmptyViewArea::openDocument()
596 {
597 KTechlab::self()->openFile( m_pViewArea );
598 }
599 //END class EmptyViewArea
600