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