From 763ef015ce995b7b46eb029af12ffab3f8810152 Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Fri, 11 Jul 2008 02:43:02 +0000 Subject: [PATCH] Improve the local layout feature by better handling of unknown textclass, unknown layouts, and switch between textclasses. * src/TextClass.h[.cpp]: add addLayoutIfNeeded() and createDefaultLayout() * src/Layout.h[.cpp]: add unknown_ to mark if a layout is a default layout for an unknown one. * src/LayoutFile.cpp: add addDefaultClass and change addLayoutFile to addLocalLayout. * src/Text.cpp: add unknown layouts that are not recognizable by a new textclass. * src/frontends/qt4/GuiToolbar.cpp: display (unknown) for unknown layouts. * src/frontends/qt4/GuiDocument.cpp: restore 'browse layout' button. * src/frontends/qt4/ui/LaTeXUi.ui: restore 'browse layout' button. * src/CutAndPaste.cpp: do not convert unknown layouts from pasted text * src/BufferParams.cpp: add a default textclass when a document with unknown textclass is loaded. git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@25555 a592a061-630c-0410-9148-cb99ea01b6c8 --- src/BufferParams.cpp | 26 ++++++------ src/CutAndPaste.cpp | 21 +++------- src/Layout.cpp | 1 + src/Layout.h | 8 ++++ src/LayoutFile.cpp | 65 +++++++++++++++++++++++------- src/LayoutFile.h | 13 ++---- src/Text.cpp | 15 +++---- src/TextClass.cpp | 66 ++++++++++++++++++++----------- src/TextClass.h | 28 +++++++++++-- src/frontends/qt4/GuiDocument.cpp | 35 ++++++++-------- src/frontends/qt4/GuiToolbar.cpp | 10 +++-- src/frontends/qt4/ui/LaTeXUi.ui | 64 +++++++++++++++--------------- 12 files changed, 207 insertions(+), 145 deletions(-) diff --git a/src/BufferParams.cpp b/src/BufferParams.cpp index 73bd2258a4..4e7952e7a1 100644 --- a/src/BufferParams.cpp +++ b/src/BufferParams.cpp @@ -463,19 +463,13 @@ string BufferParams::readToken(Lexer & lex, string const & token, string tcp; LayoutFileList & bcl = LayoutFileList::get(); if (tcp.empty() && !filepath.empty()) - tcp = bcl.addLayoutFile(classname, filepath.absFilename(), LayoutFileList::Local); + tcp = bcl.addLocalLayout(classname, filepath.absFilename()); if (!tcp.empty()) setBaseClass(tcp); - else if (bcl.haveClass(classname)) { + else setBaseClass(classname); - } else { - // a warning will be given for unknown class - setBaseClass(defaultBaseclass()); - return classname; - } - // FIXME: this warning will be given even if there exists a local .cls - // file. Even worse, the .lyx file can not be compiled or exported - // because the textclass is marked as unavilable. + // We assume that a tex class exists for local or unknown layouts so this warning + // will only be given for system layouts. if (!baseClass()->isTeXClassAvailable()) { docstring const msg = bformat(_("The layout file requested by this document,\n" @@ -487,7 +481,6 @@ string BufferParams::readToken(Lexer & lex, string const & token, frontend::Alert::warning(_("Document class not available"), msg + _("LyX will not be able to produce output.")); } - } else if (token == "\\begin_preamble") { readPreamble(lex); } else if (token == "\\begin_local_layout") { @@ -1423,13 +1416,16 @@ void BufferParams::setDocumentClass(DocumentClass const * const tc) { bool BufferParams::setBaseClass(string const & classname) { LYXERR(Debug::TCLASS, "setBaseClass: " << classname); - LayoutFileList const & bcl = LayoutFileList::get(); + LayoutFileList & bcl = LayoutFileList::get(); if (!bcl.haveClass(classname)) { docstring s = - bformat(_("The document class %1$s could not be found."), + bformat(_("The document class %1$s could not be found. " + "A default textclass with default layouts will be used. " + "LyX might not be able to produce output unless a correct " + "textclass is selected from the document settings dialog."), from_utf8(classname)); - frontend::Alert::error(_("Class not found"), s); - return false; + frontend::Alert::error(_("Document class not found"), s); + bcl.addDefaultClass(classname); } if (bcl[classname].load()) { diff --git a/src/CutAndPaste.cpp b/src/CutAndPaste.cpp index 64806157b2..a9b8fc9b30 100644 --- a/src/CutAndPaste.cpp +++ b/src/CutAndPaste.cpp @@ -509,26 +509,15 @@ void switchBetweenClasses(DocumentClass const * const oldone, ParIterator end = par_iterator_end(in); for (ParIterator it = par_iterator_begin(in); it != end; ++it) { docstring const name = it->layout().name(); - bool hasLayout = newtc.hasLayout(name); + + // the pasted text will keep their own layout name. If this layout does + // not exist in the new document, it will behave like a standard layout. + newtc.addLayoutIfNeeded(name); if (in.usePlainLayout()) it->setLayout(newtc.emptyLayout()); - else if (hasLayout) - it->setLayout(newtc[name]); else - it->setLayout(newtc.defaultLayout()); - - if (!hasLayout && name != oldtc.defaultLayoutName()) { - docstring const s = bformat( - _("Layout had to be changed from\n%1$s to %2$s\n" - "because of class conversion from\n%3$s to %4$s"), - name, it->layout().name(), - from_utf8(oldtc.name()), from_utf8(newtc.name())); - // To warn the user that something had to be done. - errorlist.push_back(ErrorItem(_("Changed Layout"), s, - it->id(), 0, - it->size())); - } + it->setLayout(newtc[name]); } // character styles diff --git a/src/Layout.cpp b/src/Layout.cpp index 61f144984e..033f5f978c 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -95,6 +95,7 @@ enum LayoutTags { Layout::Layout() { + unknown_ = false; margintype = MARGIN_STATIC; latextype = LATEX_PARAGRAPH; intitle = false; diff --git a/src/Layout.h b/src/Layout.h index f1e9b97855..5fb887302e 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -51,6 +51,9 @@ class Layout { public: /// Layout(); + /// is this layout a default layout created for an unknown layout + bool isUnknown() const { return unknown_; } + void setUnknown(bool unknown) { unknown_ = unknown; } /// Reads a layout definition from file /// \return true on success. bool read(Lexer &, TextClass const &); @@ -121,6 +124,11 @@ public: //////////////////////////////////////////////////////////////// // members //////////////////////////////////////////////////////////////// + /** Is this layout the default layout for an unknown layout? If + * so, its name will be displayed as xxx (unknown). + */ + bool unknown_; + /** Default font for this layout/environment. The main font for this kind of environment. If an attribute has INHERITED_*, it means that the value is specified by diff --git a/src/LayoutFile.cpp b/src/LayoutFile.cpp index 3c877b6675..ebbe7bb93b 100644 --- a/src/LayoutFile.cpp +++ b/src/LayoutFile.cpp @@ -194,11 +194,50 @@ void LayoutFileList::reset(LayoutFileIndex const & classname) { } -string const LayoutFileList::localPrefix = "LOCAL:"; +LayoutFileIndex LayoutFileList::addDefaultClass(string const & textclass) +{ + if (haveClass(textclass)) + return textclass; + + FileName const tempLayout = FileName::tempName(); + ofstream ofs(tempLayout.toFilesystemEncoding().c_str()); + ofs << "# This layout is automatically generated\n" + "# \\DeclareLaTeXClass{" << textclass << "}\n\n" + "Format 7\n" + "Input stdclass.inc\n\n" + "Columns 1\n" + "Sides 1\n" + "SecNumDepth 2\n" + "TocDepth 2\n" + "DefaultStyle Standard\n\n" + "Style Standard\n" + " Category MainText\n" + " Margin Static\n" + " LatexType Paragraph\n" + " LatexName dummy\n" + " ParIndent MM\n" + " ParSkip 0.4\n" + " Align Block\n" + " AlignPossible Block, Left, Right, Center\n" + " LabelType No_Label\n" + "End\n"; + ofs.close(); + + // We do not know if a LaTeX class is available for this document, but setting + // the last parameter to true will suppress a warning message about missing + // tex class. + LayoutFile * tc = new LayoutFile(textclass, textclass, "Unknown text class " + textclass, true); + if (!tc->load(tempLayout.absFilename())) + // The only way this happens is because the hardcoded layout file above + // is wrong. + LASSERT(false, /**/); + classmap_[textclass] = tc; + return textclass; +} + LayoutFileIndex - LayoutFileList::addLayoutFile(string const & textclass, string const & path, - Layout_Type type) + LayoutFileList::addLocalLayout(string const & textclass, string const & path) { // FIXME There is a bug here: 4593 // @@ -206,15 +245,7 @@ LayoutFileIndex // NOTE: latex class name is defined in textclass.layout, which can be // different from textclass string fullName = addName(path, textclass + ".layout"); - string localIndex; - if (type == Local) - localIndex = localPrefix + fullName; - - // if the local file has already been loaded, return it - if (haveClass(localIndex)) - return localIndex; - FileName const layout_file(fullName); if (layout_file.exists()) { LYXERR(Debug::TCLASS, "Adding class " << textclass << " from directory " << path); @@ -236,13 +267,19 @@ LayoutFileIndex // now, create a TextClass with description containing path information string className(sub.str(2) == "" ? textclass : sub.str(2)); LayoutFile * tmpl = - new LayoutFile(textclass, className, localIndex, true); + new LayoutFile(textclass, className, textclass, true); // This textclass is added on request so it will definitely be // used. Load it now because other load() calls may fail if they // are called in a context without buffer path information. tmpl->load(path); - classmap_[localIndex] = tmpl; - return localIndex; + // There will be only one textclass with this name, even if different + // layout files are loaded from different directories. + if (haveClass(textclass)) { + LYXERR0("Exisint textclass " << textclass << " is redefined by " << fullName); + delete classmap_[textclass]; + } + classmap_[textclass] = tmpl; + return textclass; } } } diff --git a/src/LayoutFile.h b/src/LayoutFile.h index 65feadb36f..0d4a0d7aa6 100644 --- a/src/LayoutFile.h +++ b/src/LayoutFile.h @@ -98,21 +98,16 @@ public: /// Clears the textclass so as to force it to be reloaded void reset(LayoutFileIndex const & tc); - enum Layout_Type { - System, - Local - }; - + /// add a default textclass with all standard layouts. + LayoutFileIndex addDefaultClass(std::string const & textclass); + /// add a textclass from user local directory. /// \return the identifier for the loaded file, or else an /// empty string if no file was loaded. LayoutFileIndex - addLayoutFile(std::string const & textclass, std::string const & path, - Layout_Type type); + addLocalLayout(std::string const & textclass, std::string const & path); /// a list of the available classes std::vector classList() const; - /// - static std::string const localPrefix; private: /// typedef std::map ClassMap; diff --git a/src/Text.cpp b/src/Text.cpp index a53d985787..58c2bb0d66 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -115,16 +115,11 @@ void readParToken(Buffer const & buf, Paragraph & par, Lexer & lex, layoutname = tclass.defaultLayoutName(); } - bool hasLayout = tclass.hasLayout(layoutname); - - if (!hasLayout) { - errorList.push_back(ErrorItem(_("Unknown layout"), - bformat(_("Layout '%1$s' does not exist in textclass '%2$s'\nTrying to use the default instead.\n"), - layoutname, from_utf8(tclass.name())), par.id(), 0, par.size())); - layoutname = par.usePlainLayout() ? - tclass.emptyLayoutName() : - tclass.defaultLayoutName(); - } + // When we apply an unknown layout to a document, we add this layout to the textclass + // of this document. For example, when you apply class article to a beamer document, + // all unknown layouts such as frame will be added to document class article so that + // these layouts can keep their original names. + tclass.addLayoutIfNeeded(layoutname); par.setLayout(bp.documentClass()[layoutname]); diff --git a/src/TextClass.cpp b/src/TextClass.cpp index a1928c5163..69f40e9b79 100644 --- a/src/TextClass.cpp +++ b/src/TextClass.cpp @@ -141,7 +141,7 @@ TextClass::TextClass() } -bool TextClass::readStyle(Lexer & lexrc, Layout & lay) +bool TextClass::readStyle(Lexer & lexrc, Layout & lay) const { LYXERR(Debug::TCLASS, "Reading style " << to_utf8(lay.name())); if (!lay.read(lexrc, *this)) { @@ -244,26 +244,8 @@ bool TextClass::read(FileName const & filename, ReadType rt) // Define the `empty' layout used in table cells, ert, etc. Note that // we do this before loading any layout file, so that classes can // override features of this layout if they should choose to do so. - if (rt == BASECLASS && !hasLayout(emptylayout_)) { - static char const * s = "Margin Static\n" - "LatexType Paragraph\n" - "LatexName dummy\n" - "Align Block\n" - "AlignPossible Left, Right, Center\n" - "LabelType No_Label\n" - "End"; - istringstream ss(s); - Lexer lex(textClassTags); - lex.setStream(ss); - Layout lay; - lay.setName(emptylayout_); - if (!readStyle(lex, lay)) { - // The only way this happens is because the hardcoded layout above - // is wrong. - LASSERT(false, /**/); - }; - layoutlist_.push_back(lay); - } + if (rt == BASECLASS && !hasLayout(emptylayout_)) + layoutlist_.push_back(createDefaultLayout(emptylayout_)); Lexer lexrc(textClassTags); lexrc.setFile(filename); @@ -921,6 +903,12 @@ bool TextClass::hasLayout(docstring const & n) const } +void TextClass::addLayoutIfNeeded(docstring const & n) const +{ + if (!hasLayout(n)) + layoutlist_.push_back(createDefaultLayout(n, true)); +} + Layout const & TextClass::operator[](docstring const & name) const { @@ -987,8 +975,9 @@ bool TextClass::load(string const & path) const return true; // Read style-file, provided path is searched before the system ones - FileName layout_file; - if (!path.empty()) + // If path is a file, it is loaded directly. + FileName layout_file(path); + if (!path.empty() && !layout_file.isReadableFile()) layout_file = FileName(addName(path, name_ + ".layout")); if (layout_file.empty() || !layout_file.exists()) layout_file = libFileSearch("layouts", name_, "layout"); @@ -1048,6 +1037,37 @@ bool TextClass::isPlainLayout(Layout const & layout) const } +Layout TextClass::createDefaultLayout(docstring const & name, bool unknown) const +{ + static Layout * defaultLayout = NULL; + + if (defaultLayout) { + defaultLayout->setUnknown(unknown); + defaultLayout->setName(name); + return *defaultLayout; + } + + static char const * s = "Margin Static\n" + "LatexType Paragraph\n" + "LatexName dummy\n" + "Align Block\n" + "AlignPossible Left, Right, Center\n" + "LabelType No_Label\n" + "End"; + istringstream ss(s); + Lexer lex(textClassTags); + lex.setStream(ss); + defaultLayout = new Layout; + defaultLayout->setUnknown(unknown); + defaultLayout->setName(name); + if (!readStyle(lex, *defaultLayout)) { + // The only way this happens is because the hardcoded layout above + // is wrong. + LASSERT(false, /**/); + }; + return *defaultLayout; +} + ///////////////////////////////////////////////////////////////////////// // // DocumentClassBundle diff --git a/src/TextClass.h b/src/TextClass.h index f713171cb9..25d5753672 100644 --- a/src/TextClass.h +++ b/src/TextClass.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace lyx { @@ -91,7 +92,16 @@ public: // that was based upon the same DocumentClass. (Of course, if you // really, REALLY want to make LayoutList a vector, then // you can implement custom assignment and copy constructors.) - typedef std::vector LayoutList; + // + // NOTE: Layout pointers are directly assigned to paragraphs so a + // container that does not invalidate these pointers after insertion + // is needed. + // + // NOTE: It makes sense to add unknown layouts to DocumentClass + // and make them buffer-dependent. However, this requires + // reimplementation of a lot of functions such as hasLayout + // and operator[], with little benefit. + typedef std::list LayoutList; /// The inset layouts available to this class typedef std::map InsetLayouts; /// @@ -117,6 +127,11 @@ public: bool isDefaultLayout(Layout const &) const; /// bool isPlainLayout(Layout const &) const; + /// Create a default layout for this textclass. + /** \param unknown Set to true if this layout is a default layout used to + * represent an unknown layout + */ + Layout createDefaultLayout(docstring const & name, bool unknown = false) const; /// returns a special layout for use when we don't really want one, /// e.g., in table cells Layout const & emptyLayout() const @@ -128,6 +143,8 @@ public: size_t layoutCount() const { return layoutlist_.size(); } /// bool hasLayout(docstring const & name) const; + /// add a default layout \c name if it does not exist in layoutlist_ + void addLayoutIfNeeded(docstring const & name) const; /// Layout const & operator[](docstring const & vname) const; @@ -162,6 +179,9 @@ public: // loading /////////////////////////////////////////////////////////////////// /// Sees to it the textclass structure has been loaded + /// This function will search for $classname.layout in default directories + /// and an optional path, but if path points to a file, it will be loaded + /// directly. bool load(std::string const & path = std::string()) const; /// Has this layout file been loaded yet? /// Overridden by DocumentClass @@ -196,7 +216,9 @@ protected: // members /////////////////////////////////////////////////////////////////// /// Paragraph styles used in this layout - LayoutList layoutlist_; + /// This variable is mutable because unknown layouts can be added + /// to const textclass. + mutable LayoutList layoutlist_; /// Layout file name std::string name_; /// document class name @@ -270,7 +292,7 @@ private: /// bool convertLayoutFormat(support::FileName const &, ReadType); /// \return true for success. - bool readStyle(Lexer &, Layout &); + bool readStyle(Lexer &, Layout &) const; /// void readOutputType(Lexer &); /// diff --git a/src/frontends/qt4/GuiDocument.cpp b/src/frontends/qt4/GuiDocument.cpp index 5c0f768a94..1a7780cbdd 100644 --- a/src/frontends/qt4/GuiDocument.cpp +++ b/src/frontends/qt4/GuiDocument.cpp @@ -876,6 +876,8 @@ GuiDocument::GuiDocument(GuiView & lv) this, SLOT(change_adaptor())); connect(latexModule->layoutPB, SIGNAL(clicked()), this, SLOT(browseLayout())); + connect(latexModule->layoutPB, SIGNAL(clicked()), + this, SLOT(change_adaptor())); connect(latexModule->childDocGB, SIGNAL(clicked()), this, SLOT(change_adaptor())); connect(latexModule->childDocLE, SIGNAL(textChanged(const QString &)), @@ -899,8 +901,6 @@ GuiDocument::GuiDocument(GuiView & lv) latexModule->psdriverCO->addItem(enc); } // latex classes - // FIXME hide local layout button due to bug 4812. - latexModule->layoutPB->hide(); latexModule->classCO->setModel(&classes_model_); LayoutFileList const & bcl = LayoutFileList::get(); vector classList = bcl.classList(); @@ -1241,13 +1241,22 @@ void GuiDocument::browseLayout() FileName layoutFile = support::makeAbsPath(fromqstr(file), fromqstr(bufferFilepath())); + int const ret = Alert::prompt(_("Local layout file"), + _("The layout file you have selected is a local layout\n" + "file, not one in the system or user directory. Your\n" + "document may not work with this layout if you do not\n" + "keep the layout file in the document directory."), + 1, 1, _("&Set Layout"), _("&Cancel")); + if (ret == 1) + return; + // load the layout file LayoutFileList & bcl = LayoutFileList::get(); string classname = layoutFile.onlyFileName(); - LayoutFileIndex name = bcl.addLayoutFile( + // this will update an existing layout if that layout has been loaded before. + LayoutFileIndex name = bcl.addLocalLayout( classname.substr(0, classname.size() - 7), - layoutFile.onlyPath().absFilename(), - LayoutFileList::Local); + layoutFile.onlyPath().absFilename()); if (name.empty()) { Alert::error(_("Error"), @@ -1266,6 +1275,7 @@ void GuiDocument::browseLayout() latexModule->classCO->setCurrentIndex(0); } else latexModule->classCO->setCurrentIndex(idx); + classChanged(); } @@ -1290,20 +1300,7 @@ void GuiDocument::classChanged() if (idx < 0) return; string const classname = classes_model_.getIDString(idx); - // check if this is a local layout file - if (prefixIs(classname, LayoutFileList::localPrefix)) { - int const ret = Alert::prompt(_("Local layout file"), - _("The layout file you have selected is a local layout\n" - "file, not one in the system or user directory. Your\n" - "document may not work with this layout if you do not\n" - "keep the layout file in the same directory."), - 1, 1, _("&Set Layout"), _("&Cancel")); - if (ret == 1) { - // try to reset the layout combo - setLayoutComboByIDString(bp_.baseClassID()); - return; - } - } + // FIXME Note that by doing things this way, we load the TextClass // as soon as it is selected. So, if you use the scroll wheel when // sitting on the combo box, we'll load a lot of TextClass objects diff --git a/src/frontends/qt4/GuiToolbar.cpp b/src/frontends/qt4/GuiToolbar.cpp index 5c0ab8737a..119ebba01e 100644 --- a/src/frontends/qt4/GuiToolbar.cpp +++ b/src/frontends/qt4/GuiToolbar.cpp @@ -506,7 +506,8 @@ void GuiLayoutBox::set(docstring const & layout) if (!text_class_) return; - QString const & name = toqstr((*text_class_)[layout].name()); + Layout const & lay = (*text_class_)[layout]; + QString const & name = toqstr(lay.name() + (lay.isUnknown() ? " (unknown)" : "")); if (name == currentText()) return; @@ -615,7 +616,8 @@ void GuiLayoutBox::updateContents(bool reset) // if it doesn't require the empty layout, we skip it if (name == text_class_->emptyLayoutName() && inset_ && !useEmpty) continue; - addItemSort(name, lit->category(), lyxrc.sort_layouts, lyxrc.group_layouts); + addItemSort(name + (lit->isUnknown() ? " (unknown)" : ""), + lit->category(), lyxrc.sort_layouts, lyxrc.group_layouts); } set(owner_.view()->cursor().innerParagraph().layout().name()); @@ -633,8 +635,8 @@ void GuiLayoutBox::selected(int index) { // get selection QModelIndex mindex = filterModel_->mapToSource(filterModel_->index(index, 1)); - docstring const layoutName = - qstring_to_ucs4(model_->itemFromIndex(mindex)->text()); + docstring const layoutName = rtrim( + qstring_to_ucs4(model_->itemFromIndex(mindex)->text()), " (unknown)"); owner_.setFocus(); diff --git a/src/frontends/qt4/ui/LaTeXUi.ui b/src/frontends/qt4/ui/LaTeXUi.ui index 24edcbf0d3..7d4903376b 100644 --- a/src/frontends/qt4/ui/LaTeXUi.ui +++ b/src/frontends/qt4/ui/LaTeXUi.ui @@ -160,6 +160,19 @@ + + + + Qt::Horizontal + + + + 261 + 22 + + + + @@ -190,6 +203,24 @@ + + + + + 1 + 0 + 0 + 0 + + + + Click to select a local document class definition file + + + &Local Layout... + + + @@ -200,7 +231,7 @@ - + 20 @@ -220,37 +251,6 @@ - - - - Qt::Horizontal - - - - 31 - 23 - - - - - - - - - 1 - 0 - 0 - 0 - - - - Click to select a local document class definition file - - - &Local Layout... - - - -- 2.39.2