├── BeamerQt.sh ├── Development Ideas.lyx ├── LICENSE ├── README.md ├── TODO.txt ├── TestBeamer.lyx ├── core ├── beamerBlock.py ├── beamerDocument.py ├── beamerSlide.py ├── configFile.py ├── frontMatter.py ├── preamble.tex ├── template.py └── xmlutils.py ├── gui ├── About.ui ├── AboutWidget.py ├── Configurator.ui ├── ContentItem.ui ├── ContentItem2.ui ├── ContentItems │ ├── Image │ │ ├── ContentItemImage.py │ │ ├── ImageBrowse.ui │ │ ├── ItemImage.ui │ │ ├── add-image.png │ │ └── imagebrowse.py │ ├── RTF │ │ ├── ContentItemRTF.py │ │ └── ItemRTF.ui │ └── Text │ │ ├── ContentItemText.py │ │ └── ItemText.ui ├── ContentWidget.ui ├── DocumentTab.ui ├── DualSlider.py ├── FrameWidget.ui ├── FrontMatter │ ├── FrontMatterWidget.ui │ ├── Presets │ │ └── Base.txt │ └── frontmatterwidget.py ├── MainWindow.ui ├── RecentFiles.py ├── Slide.py ├── SlidePrev.ui ├── SlideWidget.ui ├── Slidebar.ui ├── Test.xml ├── ThumbListWidget.py ├── __init__.py ├── configurator.py ├── contentitem.py ├── contentwidget.py ├── framewidget.py ├── framexml.py ├── guidialogs.py ├── icons │ ├── BQTIcon.png │ ├── Donate_QR Code.png │ ├── Logo.svg │ ├── add-image.png │ ├── align-center.png │ ├── align-justified.png │ ├── align-left.png │ ├── align-right.png │ ├── align_center.png │ ├── align_default.png │ ├── align_left.png │ ├── align_right.png │ ├── arrow-down.png │ ├── arrow-left.png │ ├── arrow-right.png │ ├── arrow-up.png │ ├── base.png │ ├── blocks_column.png │ ├── cancel.png │ ├── clipboard.png │ ├── column_blocks.png │ ├── content_widget_icons.svg │ ├── downarrow.png │ ├── format-bold.png │ ├── format-italic.png │ ├── format-item.png │ ├── format_numbered.png │ ├── four_blocks.png │ ├── frames_layout.svg │ ├── icon.png │ ├── leftarrow.png │ ├── newBlock.png │ ├── restore_blocks.png │ ├── rightarrow.png │ ├── standard.png │ ├── title_frame.png │ ├── trash.png │ ├── two_columns.png │ ├── two_rows.png │ └── uparrow.png ├── mainwindow.py ├── resources.qrc ├── slidebar.py └── slidewidget.py ├── icon.ico ├── main.py └── templates ├── annarbor.xml ├── antibes.xml ├── bergen.xml ├── berkeley.xml ├── berlin.xml ├── boadilla.xml ├── cambridgeus.xml ├── copenhagen.xml ├── darmstadt.xml ├── default.xml ├── dresden.xml ├── frankfurt.xml ├── genTemplate.py ├── goettingen.xml ├── hannover.xml ├── ilmenau.xml ├── juanlespins.xml ├── luebeck.xml ├── madrid.xml ├── malmoe.xml ├── marburg.xml ├── montpellier.xml ├── paloalto.xml ├── pittsburgh.xml ├── rochester.xml ├── singapore.xml ├── szeged.xml ├── templatelist.txt └── warsaw.xml /BeamerQt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname "$0") 4 | 5 | python3 main.py 6 | -------------------------------------------------------------------------------- /Development Ideas.lyx: -------------------------------------------------------------------------------- 1 | #LyX 2.3 created this file. For more info see http://www.lyx.org/ 2 | \lyxformat 544 3 | \begin_document 4 | \begin_header 5 | \save_transient_properties true 6 | \origin unavailable 7 | \textclass article 8 | \use_default_options true 9 | \maintain_unincluded_children false 10 | \language english 11 | \language_package default 12 | \inputencoding auto 13 | \fontencoding global 14 | \font_roman "default" "default" 15 | \font_sans "default" "default" 16 | \font_typewriter "default" "default" 17 | \font_math "auto" "auto" 18 | \font_default_family default 19 | \use_non_tex_fonts false 20 | \font_sc false 21 | \font_osf false 22 | \font_sf_scale 100 100 23 | \font_tt_scale 100 100 24 | \use_microtype false 25 | \use_dash_ligatures true 26 | \graphics default 27 | \default_output_format default 28 | \output_sync 0 29 | \bibtex_command default 30 | \index_command default 31 | \paperfontsize default 32 | \use_hyperref false 33 | \papersize default 34 | \use_geometry false 35 | \use_package amsmath 1 36 | \use_package amssymb 1 37 | \use_package cancel 1 38 | \use_package esint 1 39 | \use_package mathdots 1 40 | \use_package mathtools 1 41 | \use_package mhchem 1 42 | \use_package stackrel 1 43 | \use_package stmaryrd 1 44 | \use_package undertilde 1 45 | \cite_engine basic 46 | \cite_engine_type default 47 | \use_bibtopic false 48 | \use_indices false 49 | \paperorientation portrait 50 | \suppress_date false 51 | \justification true 52 | \use_refstyle 1 53 | \use_minted 0 54 | \index Index 55 | \shortcut idx 56 | \color #008000 57 | \end_index 58 | \secnumdepth 3 59 | \tocdepth 3 60 | \paragraph_separation indent 61 | \paragraph_indentation default 62 | \is_math_indent 0 63 | \math_numbering_side default 64 | \quotes_style english 65 | \dynamic_quotes 0 66 | \papercolumns 1 67 | \papersides 1 68 | \paperpagestyle default 69 | \tracking_changes false 70 | \output_changes false 71 | \html_math_output 0 72 | \html_css_as_file 0 73 | \html_be_strict false 74 | \end_header 75 | 76 | \begin_body 77 | 78 | \begin_layout Section 79 | Development goals 80 | \end_layout 81 | 82 | \begin_layout Subsection 83 | Create zoom out frames 84 | \end_layout 85 | 86 | \begin_layout Standard 87 | https://stackoverflow.com/questions/57713795/zoom-in-and-out-in-widget 88 | \end_layout 89 | 90 | \begin_layout Subsection 91 | Multi-tab documents 92 | \end_layout 93 | 94 | \begin_layout Subsection 95 | Create Text Edit with Widgets support 96 | \end_layout 97 | 98 | \begin_layout Standard 99 | https://forum.qt.io/topic/61098/widgets-inside-a-text-editor 100 | \end_layout 101 | 102 | \begin_layout Subsection 103 | Extract the formatting and contents from the Text Edit and generate LaTeX 104 | equivalent 105 | \end_layout 106 | 107 | \begin_layout Subsection 108 | Interoperate information from xml-like objects and gui widgets 109 | \end_layout 110 | 111 | \begin_layout Subsection 112 | Generate Zip files with folder structure 113 | \end_layout 114 | 115 | \begin_layout Standard 116 | The system will have a temporary folder to work with. 117 | When saving, it will compress the folder and save it at the destination. 118 | When opening, it will decompress in a temporal folder. 119 | \end_layout 120 | 121 | \begin_layout Standard 122 | External files will be stored in the zip file. 123 | The system will keep track of the original file and, if lost, generate 124 | a warning and use only the local one. 125 | \end_layout 126 | 127 | \begin_layout Section 128 | Goals - steps 129 | \end_layout 130 | 131 | \begin_layout Itemize 132 | Reading/Saving slides vs Internal object: 05-06-2024 133 | \end_layout 134 | 135 | \begin_deeper 136 | \begin_layout Itemize 137 | The current prototype handles the reading/saving steps inside the GUI elements. 138 | 139 | \end_layout 140 | 141 | \begin_layout Itemize 142 | I need to make it in an object that handles this process independently, 143 | so that it can be invoked when we need to export to PDF without displaying 144 | the GUI elements in the process. 145 | \end_layout 146 | 147 | \begin_layout Itemize 148 | Each GUI element should have the corresponding handler. 149 | \end_layout 150 | 151 | \begin_layout Itemize 152 | New modules should have the pair, handler/GUI. 153 | \end_layout 154 | 155 | \begin_layout Itemize 156 | 157 | \series bold 158 | Solve this issue before adding more features 159 | \end_layout 160 | 161 | \begin_deeper 162 | \begin_layout Itemize 163 | Solved 164 | \end_layout 165 | 166 | \begin_layout Itemize 167 | The behavior works better, however, it lost the preview pixmap while dragging. 168 | 169 | \end_layout 170 | 171 | \end_deeper 172 | \end_deeper 173 | \begin_layout Standard 174 | \begin_inset Separator plain 175 | \end_inset 176 | 177 | 178 | \end_layout 179 | 180 | \begin_layout Itemize 181 | Saving temporal file: 05-08-2024 182 | \end_layout 183 | 184 | \begin_deeper 185 | \begin_layout Itemize 186 | Pass through the Slide object, and generate the XML recursively. 187 | \end_layout 188 | 189 | \begin_layout Itemize 190 | Repeat the same to opening the file. 191 | \end_layout 192 | 193 | \end_deeper 194 | \begin_layout Standard 195 | \begin_inset Separator plain 196 | \end_inset 197 | 198 | 199 | \end_layout 200 | 201 | \end_body 202 | \end_document 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeamerQt 2 | BeamerQT is a user-friendly graphical interface designed to facilitate the creation of Beamer presentations without manually editing LaTeX code associated with the Slides. It provides a comprehensive set of features that allow users to define layouts, insert content (including text, blocks, and images), and configure some advance settings of the theme. BeamerQT provides both beginners and advanced LaTeX users the ability to create amazing presentations and focus in the contents rather than in the code. 3 | 4 | 5 | 6 | # Features 7 | BeamerQT features a graphical user interface that provides easy access to most desired Beamer/LaTeX features without adding LaTeX code. 8 | 9 | ## Layout Selection 10 | BeamerQT provides a range of predefined layout schemes inspired by common presentation tools such as PowerPoint or LibreOffice Impress. Instead of manually writing LaTeX code for columns and blocks, the user can simply select a layout and BeamerQT will automatically insert and manage the required Beamer columns and blocks. 11 | 12 | Key capabilities include: 13 | 14 | * Automatic creation of columns and blocks based on the chosen layout. 15 | * A slider control to adjust column widths dynamically, without manual code edits. 16 | * Seamless reconfiguration of the slide layout with minimal user intervention. 17 | 18 | 19 | 20 | 21 | 22 | 23 | ## Slides 24 | 25 | Each slide can contain a title, a subtitle, and a set of blocks for content. Additionally, slides can be configured as either a new section or subsection, enabling automatic insertion of corresponding section or subsection titles into the presentation. 26 | 27 | Key features: 28 | 29 | * Easy input of slide title and subtitle. 30 | * Marking a slide as a section or subsection to structure the presentation. 31 | * Automatic adjustment of slide-level formatting options. 32 | 33 | ## Slides List 34 | 35 | The Slides List provides an overview of the entire presentation, showing each slide’s position, number, and title. Sections and subsections are clearly marked, assisting in navigation and organization. Users can reorder, duplicate, copy, or delete slides as needed, ensuring efficient slide management. 36 | 37 | ## Blocks 38 | 39 | Blocks are fundamental units of content in BeamerQT. The tool supports various block types—such as Block, Alert, Example, or plain text blocks—through a simple radio-button interface. This approach eliminates the need to write LaTeX commands manually. 40 | 41 | Core functionalities of blocks include: 42 | 43 | * Selection of block type (Block, Alert, Example, or plain text). 44 | * Position controls for rearranging blocks within the layout. 45 | * A dedicated button for removing the block from the slide. 46 | * A title field and text input area for each block, with multiline support. 47 | * Automatic line breaks or retention of manual line breaks depending on user input. 48 | * Multiple sub-blocks 49 | 50 | 51 | 52 | ## Sub-blocks 53 | 54 | Sub-blocks allow for more granular content organization within a block. Each block contains at least one sub-block (generally text-based), and users can add multiple sub-blocks as needed. 55 | 56 | Sub-block features include: 57 | 58 | * Arrangement in up to four columns, with horizontal navigation buttons to reorder sub-blocks. 59 | * A slider to adjust column widths, offering flexible layout customization. 60 | * Alignment controls for each sub-block (left, center, right, or default). 61 | 62 | ## Image sub-block 63 | 64 | The image sub-block is a specialized sub-block type for inserting images. BeamerQT supports bitmap files (e.g., .jpg, .png), vector images (.svg), and .pdf files. For .svg images, BeamerQT utilizes Inkscape to convert them to .pdf format, ensuring seamless integration into the final presentation (tested Linux systems only). 65 | 66 | Image sub-block features: 67 | 68 | * Adjustable image sizing as a percentage of the sub-block’s width. 69 | * Automatic adaptation to layout changes for consistently scaled visuals. 70 | * Compatibility with multiple image formats, ensuring flexibility in presentation design. 71 | 72 | ## Front-matter 73 | 74 | The front-matter section allows for easy configuration of presentation-wide settings. Users can define the presentation title, author names, and customize the LaTeX preamble. Additionally, advanced features can be enabled to further refine the overall look and structure of the presentation, such as: 75 | 76 | * Changing the aspect ratio (4:3 to 16:9). 77 | * Creating title frames for each section. 78 | * Automatically generating an outline frame for each section. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | ## LaTeX generation 89 | 90 | When the user is satisfied with the content and layout, clicking the **Generate LaTeX** button exports the presentation to LaTeX and runs pdflatex to compile a PDF. The resulting PDF is then displayed, enabling immediate review. 91 | 92 | The **LaTeX folder** button opens the output directory, allowing for further customization or integration with other tools. Note that each LaTeX generation overwrites files in the output folder. 93 | 94 | 95 | ## File format 96 | 97 | BeamerQT uses a .bqt file format, which is essentially a zipped directory containing all necessary metadata, such as: 98 | 99 | * An XML file with presentation details. 100 | * Preview images of slides. 101 | * (Future feature) Embedded images and custom themes to ensure portability. 102 | 103 | This approach ensures that .bqt files can be easily shared, backed up, and edited across different systems without losing essential data. 104 | 105 | 106 | ## Installation 107 | 108 | * Windows 109 | 110 | Install MikTex: 111 | 112 | https://miktex.org/download 113 | 114 | Install BeamerQT: 115 | 116 | https://sourceforge.net/projects/beamerqt/ 117 | 118 | * Linux 119 | Install python3, TexLive and Inkscape, according to your distribution: 120 | 121 | 122 | Debian based: 123 | ``` 124 | apt-get install python3 texlive-beamer inkscape 125 | ``` 126 | Install PyQt6: 127 | ``` 128 | pip install pyqt6 129 | ``` 130 | 131 | 132 | Run BeamerQT: 133 | 134 | Download the source code of BeamerQT from this repository. 135 | 136 | Open a terminal in the directory that contains the file main.py 137 | ``` 138 | python3 main.py 139 | ``` 140 | 141 | 142 | ## Example video: 143 | 144 | https://www.youtube.com/watch?v=XQKJbuT8q1g 145 | 146 | ## Screenshots: 147 | ### BeamerQT GUI 148 | 149 | 150 | ### PDF Output 151 | 152 | 153 | 154 | ## Donate 155 | Please donate to help me developing this software. Paypal donation link: 156 | 157 | https://www.paypal.com/donate/?business=2PP5H8Z8L5E8E&no_recurring=0&item_name=Support+the+development+of+BeamerQT¤cy_code=USD 158 | 159 | 160 | ## Science Fiction Book 161 | If you enjoyed this, you might also like my science fiction book, Synapses: The Chaos of Order. 162 | 163 | http://synapsesbook.wordpress.com/ 164 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | Pending activities 2 | 3 | 4 | [X] Add recent files 5 | 6 | [X] Put the elements in Box if there are more than 1 7 | 8 | [] Enable alignment of elements 9 | 10 | [] Enable draging elements along blocks 11 | 12 | [] Save images inside the file .bqt 13 | 14 | [X] Save slide previews inside the file .bqt 15 | 16 | [] Create class for converters and integrate it to the core 17 | 18 | [X] Title of file in the bar title 19 | 20 | [] Open files in new window, except when the current file is new 21 | 22 | [] Allow change of slides while moving on the slide prev with keyboard 23 | 24 | -------------------------------------------------------------------------------- /core/beamerBlock.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import sys 20 | import os 21 | 22 | import xml.etree.ElementTree as ET 23 | 24 | import importlib 25 | 26 | import json 27 | 28 | from core.xmlutils import * 29 | 30 | 31 | class BeamerBlock(): 32 | 33 | 34 | def __init__(self): 35 | 36 | self.Title = "" 37 | 38 | self.nombre = "" 39 | 40 | self.Text = "" 41 | 42 | self.ColumnNumber = -1 43 | 44 | self.Expanded = False 45 | 46 | self.SubBlocks = [] 47 | 48 | self.BlockType = "Normal" 49 | 50 | self.ColumnCount = 1 51 | 52 | self.TableMode = True 53 | 54 | self.BlockWidth = 100 # percentage 55 | 56 | self.DebugMode = False 57 | 58 | self.ColumnProportions = [100, 100, 100, 100] 59 | 60 | # for interpolation of column size 61 | self.mval = [1.16, 1.17, 1.16] 62 | self.bval = [-1.8, -2.4, -2.8] 63 | 64 | 65 | 66 | 67 | def GetXMLContent(self): 68 | ContentXML = ET.Element('Block', id='block_'+self.nombre) 69 | BlockTitle = ET.SubElement(ContentXML, 'BlockTitle') 70 | BlockTitle.text = self.Title 71 | 72 | BlockText = ET.SubElement(ContentXML, 'BlockText') 73 | BlockText.text = self.Text 74 | 75 | BlockType = ET.SubElement(ContentXML, 'BlockType') 76 | BlockType.text = self.BlockType 77 | 78 | 79 | ColsCount = ET.SubElement(ContentXML, 'ColumnCount') 80 | ColsCount.text = str(self.ColumnCount) 81 | 82 | ColsProp = ET.SubElement(ContentXML, 'ColumnProportions') 83 | ColsProp.text = str(self.ColumnProportions) 84 | 85 | 86 | for Elem in self.SubBlocks: 87 | blockElem = Elem.GetXMLContent() 88 | ContentXML.append(blockElem) 89 | 90 | 91 | 92 | 93 | self.ContentXML = ContentXML 94 | 95 | return ContentXML 96 | 97 | 98 | 99 | def ReadXMLContent(self, xblock): 100 | 101 | xmlblock = xmlutils(xblock) 102 | 103 | self.Title = xblock.findall('BlockTitle')[0].text 104 | self.Text = xblock.findall('BlockText')[0].text 105 | 106 | self.ColumnCount = int( xblock.findall('ColumnCount')[0].text ) 107 | 108 | self.BlockType = xblock.findall('BlockType')[0].text 109 | 110 | ColProportions = xmlblock.GetField('ColumnProportions', '[100,100,100,100]') 111 | 112 | self.ColumnProportions = json.loads(ColProportions) 113 | 114 | 115 | 116 | self.SubBlocks.clear() 117 | 118 | for xmlWidget in xblock.findall('ItemWidget'): 119 | itemtype = xmlWidget.get('ItemType') 120 | 121 | Item = self.GetItemType(itemtype) 122 | Item.ReadXMLContent(xmlWidget) 123 | 124 | self.SubBlocks.append(Item) 125 | 126 | 127 | 128 | def GetItemType(self, itemtype): 129 | 130 | typeloc = 'gui.ContentItems.'+itemtype+ ".ContentItem"+itemtype 131 | 132 | module = importlib.import_module(typeloc) 133 | itemClass = getattr(module, "item" + itemtype ) 134 | Item = itemClass() 135 | 136 | return Item 137 | 138 | 139 | def GenLatex(self, arg = None): 140 | latexcontent = [] 141 | 142 | if self.Title == None: 143 | self.Title = "" 144 | 145 | # Add code to starting the block 146 | if self.BlockType == "Normal": 147 | latexcontent.append( "\\begin{block}{" + str(self.Title) + "}" ) 148 | 149 | if self.BlockType == "Example": 150 | latexcontent.append( "\\begin{exampleblock}{" + str(self.Title) + "}" ) 151 | 152 | 153 | if self.BlockType == "Alert": 154 | latexcontent.append( "\\begin{alertblock}{" + str(self.Title) + "}" ) 155 | 156 | 157 | ## Create table mode 158 | ## Create tabular letters 159 | k = 0 160 | 161 | if len(self.SubBlocks) > 1: 162 | self.TableMode = True 163 | else: 164 | self.TableMode = False 165 | 166 | 167 | # if self.TableMode: 168 | 169 | # latexcontent.append("\\setlength\\tabcolsep{0pt}") 170 | # latexcontent.append("\\begin{tabular}{lllll}") 171 | 172 | LastCol = 0 173 | 174 | for item in self.SubBlocks: 175 | 176 | # recalculate width 177 | 178 | colsize = 10*self.BlockWidth/100 179 | 180 | divisions = min(len(self.SubBlocks),self.ColumnCount) 181 | 182 | if divisions > 1: 183 | colsize = colsize*self.mval[divisions-2]+self.bval[divisions-2] 184 | 185 | 186 | 187 | 188 | SBSize = self.BlockWidth/divisions 189 | SBSize = (self.ColumnProportions[LastCol]*colsize/divisions)/100 # cm 190 | SBSize = round((self.ColumnProportions[LastCol]//divisions)/100, 2) 191 | 192 | # self.TableMode = False 193 | if self.TableMode: 194 | latexcontent.append("{\\fboxsep 0pt%") 195 | ### Need to recalculate the formula, since it is not that linear! 196 | 197 | item.MaxItemSize = SBSize 198 | 199 | 200 | LastCol += 1 201 | 202 | if LastCol == self.ColumnCount: 203 | LastCol = 0 204 | 205 | 206 | 207 | if self.DebugMode and self.TableMode: 208 | latexcontent.append("\\fbox{%") 209 | 210 | if self.TableMode: 211 | # latexcontent.append("\\begin{minipage}[b]{"+str(SBSize)+"\linewidth}") 212 | # latexcontent.append("\\begin{minipage}[t]{0.33\linewidth}") 213 | latexcontent.append("\\begin{minipage}[c]{"+str(SBSize)+"\\linewidth}%") 214 | 215 | 216 | # Alignment 217 | if item.Alignment == "Left": 218 | latexcontent.append("\\begin{flushleft}") 219 | if item.Alignment == "Right": 220 | latexcontent.append("\\begin{flushright}") 221 | if item.Alignment == "Center": 222 | latexcontent.append("\\begin{center}") 223 | 224 | itemlatex = item.GenLatex() 225 | latexcontent.extend(itemlatex) 226 | 227 | 228 | # end Alignment 229 | 230 | if item.Alignment == "Left": 231 | latexcontent.append("\\end{flushleft}") 232 | if item.Alignment == "Right": 233 | latexcontent.append("\\end{flushright}") 234 | if item.Alignment == "Center": 235 | latexcontent.append("\\end{center}") 236 | 237 | 238 | 239 | if self.TableMode: 240 | latexcontent.append("\\end{minipage}}%") 241 | # latexcontent.append("}%") 242 | 243 | 244 | if self.DebugMode and self.TableMode: 245 | latexcontent.append("}%") 246 | 247 | # if self.TableMode: 248 | # latexcontent.append(" & ") 249 | k += 1 250 | 251 | if k == self.ColumnCount: 252 | # if self.TableMode: 253 | # latexcontent.append("\\tabularnewline") 254 | # else: 255 | latexcontent.append("\\"+"\\") 256 | 257 | 258 | # if self.TableMode: 259 | # latexcontent.append("\\end{tabular}") 260 | 261 | 262 | if self.BlockType == "Normal": 263 | latexcontent.append("\\end{block}") 264 | 265 | if self.BlockType == "Example": 266 | latexcontent.append("\\end{exampleblock}") 267 | 268 | 269 | if self.BlockType == "Alert": 270 | latexcontent.append("\\end{alertblock}") 271 | 272 | 273 | 274 | 275 | return latexcontent 276 | 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /core/beamerDocument.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import sys 20 | import os 21 | import xml.etree.ElementTree as ET 22 | import tempfile 23 | import zipfile 24 | import shutil 25 | import platform 26 | 27 | import subprocess 28 | 29 | import threading 30 | import time 31 | 32 | from core.beamerSlide import * 33 | 34 | from core.frontMatter import * 35 | 36 | from core.template import * 37 | 38 | class beamerDocument(): 39 | 40 | def __init__(self, Path): 41 | 42 | # Define a path inside the given Path. It is expected to be 43 | # a temporary folder. 44 | 45 | self.DocLocation = tempfile.mkdtemp(prefix= Path+"/" ) 46 | 47 | # Store everything in DocLocation 48 | 49 | 50 | self.NewFile = True 51 | self.RealLocation = "" 52 | 53 | self.Template = BeamerTemplate() 54 | 55 | self.FrontMatter = frontMatter() 56 | 57 | self.CreateFolders() 58 | 59 | self.Slides = [] 60 | 61 | self.ExportCounts = 0 62 | 63 | self.Status = False 64 | 65 | self.Message = "" 66 | 67 | self.Config = None 68 | 69 | 70 | def ReIndexSlides(self): 71 | k = 0 72 | for slide in self.Slides: 73 | slide.Number = k 74 | k += 1 75 | 76 | def NewSlide(self, Location=-1): 77 | 78 | newslide = BeamerSlide() 79 | 80 | newslide.Document = self 81 | 82 | if Location == -1: 83 | self.Slides.append(newslide) 84 | else: 85 | self.Slides.insert(Location, newslide) 86 | 87 | self.ReIndexSlides() 88 | 89 | return newslide 90 | 91 | 92 | 93 | def RemoveSlide(self, Location): 94 | if len(self.Slides) > Location: 95 | return self.Slides.pop(Location) 96 | else: 97 | return None 98 | 99 | self.ReIndexSlides() 100 | 101 | 102 | def InsertSlide(self, Slide, Location=-1): 103 | 104 | Slide.Document = self 105 | 106 | if Location == -1 or Location >= len(self.Slides): 107 | self.Slides.append(Slide) 108 | else: 109 | self.Slides.insert(Location, Slide) 110 | 111 | self.ReIndexSlides() 112 | 113 | 114 | def SaveXML (self): 115 | 116 | Documento = ET.Element('BeamerDoc') 117 | 118 | BT = self.Template.GetXMLContent() 119 | Documento.append(BT) 120 | 121 | FM = self.FrontMatter.GetXMLContent() 122 | Documento.append(FM) 123 | 124 | for slide in self.Slides: 125 | Documento.append(slide.GetXMLContent()) 126 | slide.savePreview() 127 | 128 | tree = ET.ElementTree(Documento) 129 | ET.indent(tree, ' ') 130 | 131 | tree.write( os.path.join(self.docfolder, "BeamerQt.xml"), encoding="utf-8", xml_declaration=True) 132 | 133 | 134 | def ReadXML(self, xmlDocument): 135 | print('Opening XML file') 136 | tree = ET.parse(xmlDocument) 137 | root = tree.getroot() 138 | 139 | BT = root.findall('Template')[0] 140 | self.Template.ReadXMLContent(BT) 141 | 142 | FM = root.findall('FrontMatter')[0] 143 | 144 | self.FrontMatter.ReadXMLContent(FM) 145 | 146 | 147 | for subslide in root.findall('Frame'): 148 | newslide = self.NewSlide() 149 | newslide.ReadXMLContent(subslide) 150 | 151 | print(newslide.Title) 152 | 153 | 154 | 155 | def WriteFile(self, filename): 156 | self.RealLocation = filename 157 | 158 | self.SaveXML() 159 | 160 | #Now compress the whole Doc folder 161 | 162 | current_working_directory = os.getcwd() 163 | # os.chdir(self.DocLocation) 164 | 165 | tmpfile = os.path.join(self.DocLocation, "beamerqt") 166 | 167 | # subprocess.call("zip -r ../beamerqt.bqt * ", shell=True) 168 | shutil.make_archive( tmpfile , 'zip', self.docfolder) 169 | 170 | shutil.copy(tmpfile+".zip", filename) 171 | 172 | # os.chdir(current_working_directory) 173 | 174 | self.NewFile = False 175 | 176 | 177 | def ReadFile(self, filename): 178 | self.RealLocation = filename 179 | self.NewFile = False 180 | 181 | with zipfile.ZipFile(filename, 'r') as zip_ref: 182 | zip_ref.extractall(self.docfolder) 183 | 184 | print("Extracted file in :" + self.docfolder) 185 | 186 | self.ReadXML( os.path.join(self.docfolder, "BeamerQt.xml" ) ) 187 | 188 | print("After read") 189 | for slide in self.Slides: 190 | print(slide.Title) 191 | 192 | 193 | 194 | 195 | def CreateFolders(self): 196 | 197 | self.latexfolder = os.path.join(self.DocLocation, "LaTeX") 198 | os.makedirs(self.latexfolder, exist_ok=True) 199 | 200 | self.docfolder = os.path.join(self.DocLocation, "Doc") 201 | os.makedirs(self.docfolder, exist_ok=True) 202 | 203 | self.mediafolder = os.path.join(self.docfolder, "Media") 204 | os.makedirs(self.mediafolder, exist_ok=True) 205 | 206 | self.slidesprev = os.path.join(self.mediafolder, "SlidesPrev") 207 | os.makedirs(self.slidesprev, exist_ok=True) 208 | 209 | 210 | 211 | def WriteLines(self, lines, outputfile): 212 | 213 | for line in lines: 214 | try: 215 | outputfile.write(line) 216 | outputfile.write('\n') 217 | except: 218 | None 219 | 220 | 221 | def ShowLaTeXFolder(self): 222 | LocalSystem = platform.system() 223 | 224 | if LocalSystem == "Windows": 225 | os.startfile(self.latexfolder) 226 | else: 227 | subprocess.call(('xdg-open', self.latexfolder)) 228 | 229 | 230 | def GenLaTeX(self): 231 | self.Status = True 232 | self.Message = "Generating LaTeX..." 233 | x = threading.Thread(target=self.GenLaTeXThread, args=(self,)) 234 | x.start() 235 | 236 | def GenLaTeXThread(self, arg): 237 | 238 | filename = os.path.join(self.latexfolder, "output.tex") 239 | # outputfile = open( os.path.join(self.DocLocation, "Output.tex"), 'w' ) 240 | 241 | 242 | outputfile = open(filename, 'w' ) 243 | 244 | 245 | preamble = open( os.path.join( os.path.dirname(__file__) , "preamble.tex" ), 'r').readlines() 246 | 247 | outputfile.write(self.FrontMatter.GenLaTeXOptions()) 248 | 249 | outputfile.writelines(preamble) 250 | 251 | 252 | # add template 253 | latexcontent = self.Template.GenLaTeX() 254 | self.WriteLines(latexcontent, outputfile) 255 | 256 | self.WriteLines([self.FrontMatter.Preamble], outputfile) 257 | 258 | 259 | 260 | # add front matter 261 | latexcontent = self.FrontMatter.GenLaTeX() 262 | self.WriteLines(latexcontent, outputfile) 263 | 264 | 265 | self.WriteLines(["\\begin{document}", "\\makebeamertitle"], outputfile) 266 | 267 | 268 | 269 | 270 | for slide in self.Slides: 271 | latexcontent = slide.GenLaTeX() 272 | self.WriteLines(latexcontent, outputfile) 273 | 274 | 275 | outputfile.write("\\end{document}") 276 | 277 | outputfile.close() 278 | 279 | self.ExportPDF() 280 | 281 | 282 | 283 | 284 | def ExportPDF(self): 285 | 286 | self.Message = "Generating PDF document..." 287 | 288 | LocalSystem = platform.system() 289 | 290 | current_working_directory = os.getcwd() 291 | 292 | os.chdir(self.latexfolder) 293 | 294 | 295 | subprocess.call("pdflatex -interaction=nonstopmode output.tex", shell=True) 296 | 297 | 298 | if self.ExportCounts == 0: 299 | # some processes need pdflatex to run twice! 300 | subprocess.call("pdflatex -interaction=nonstopmode output.tex", shell=True) 301 | 302 | self.ExportCounts += 1 303 | 304 | 305 | if LocalSystem == "Windows": 306 | os.startfile('output.pdf') 307 | else: 308 | subprocess.call(('xdg-open', 'output.pdf')) 309 | 310 | 311 | self.Status = True 312 | 313 | if os.path.exists('output.pdf'): 314 | self.Message = "Document generated" 315 | copylocation = self.RealLocation.replace("bqt", "")+"pdf" 316 | shutil.copy( os.path.join(self.latexfolder, 'output.pdf') , copylocation) 317 | print("Exported file to: " + copylocation) 318 | else: 319 | self.Message = "Error generating PDF document" 320 | 321 | os.chdir(current_working_directory) 322 | 323 | 324 | 325 | time.sleep(10) 326 | self.Status = False 327 | self.Message = "" 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /core/beamerSlide.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import sys 20 | import os 21 | 22 | import xml.etree.ElementTree as ET 23 | 24 | from core.beamerBlock import * 25 | 26 | from core.xmlutils import * 27 | 28 | class BeamerSlide(): 29 | 30 | def __init__(self): 31 | 32 | self.Title = "" 33 | self.Subtitle = "" 34 | self.SectionLabel = "" 35 | self.TitleVisible = True 36 | self.nombre = "" 37 | 38 | self.Text = "" 39 | 40 | self.Blocks = [] 41 | self.Columns = [[],[]] 42 | 43 | self.CurrentLayout = "layout_standard" 44 | 45 | self.Modified = False 46 | 47 | self.Preview = None 48 | 49 | self.TitleMode = "Normal" 50 | 51 | self.LeftColumnProportion = 100 52 | 53 | self.Document = None 54 | 55 | self.Number = -1 56 | 57 | 58 | 59 | def setPreview(self, pixmap): 60 | self.Preview = pixmap 61 | self.Modified = True 62 | 63 | def savePreview(self): 64 | if self.Preview != None and self.Document != None and self.Number != -1: 65 | slidename = os.path.join(self.Document.slidesprev, "Slide_"+str(self.Number)+".png") 66 | print("Saving slide prev at: " + slidename) 67 | self.Preview.save(slidename, "PNG", 0) 68 | 69 | def getSlideName(self): 70 | slidename = "" 71 | 72 | if self.Document != None: 73 | slidename = os.path.join(self.Document.slidesprev, "Slide_"+str(self.Number)+".png") 74 | 75 | return slidename 76 | 77 | 78 | 79 | 80 | def GetXMLContent(self): 81 | 82 | FrameXML = ET.Element('Frame', id='frame_0') 83 | 84 | xmlblock = xmlutils(FrameXML) 85 | 86 | TitleBar = ET.SubElement(FrameXML, 'TitleBar') 87 | TitleVisible = ET.SubElement(TitleBar, 'Visible') 88 | TitleVisible.text = str(self.TitleVisible) 89 | 90 | TitleBar.text = self.Title 91 | 92 | SubTitleBar = ET.SubElement(FrameXML, 'SubTitleBar') 93 | SubTitleBar.text = self.Subtitle 94 | 95 | xmlblock.SetField('SectionLabel', self.SectionLabel) 96 | 97 | 98 | TitleMode = ET.SubElement(FrameXML, 'TitleMode') 99 | TitleMode.text = self.TitleMode 100 | 101 | 102 | FrameLayout = ET.SubElement(FrameXML, 'FrameLayout') 103 | FrameLayout.text = self.CurrentLayout 104 | 105 | # colAttributes={"id":"0", "Proportion":str(self.LeftColumnProportion)} 106 | 107 | ColumnProportion = ET.SubElement(FrameXML, 'ColumnProportion') 108 | ColumnProportion.text = str(self.LeftColumnProportion) 109 | 110 | 111 | ColumnXML0 = ET.SubElement(FrameXML, 'Column', id='0') 112 | ColumnXML1 = ET.SubElement(FrameXML, 'Column', id='1') 113 | 114 | ColXML = [ColumnXML0, ColumnXML1] 115 | 116 | for k in range(2): 117 | for block in self.Columns[k]: 118 | BlockElem = block.GetXMLContent() 119 | ColXML[k].append(BlockElem) 120 | 121 | 122 | # FrameXML.append(ColXML[0]) 123 | # FrameXML.append(ColXML[1]) 124 | 125 | 126 | # for block in self.Blocks: 127 | # BlockElem = block.GetXMLContent() 128 | # FrameXML.append(BlockElem) 129 | 130 | 131 | return FrameXML 132 | 133 | 134 | def ReadXMLContent(self, xblock): 135 | 136 | xmlblock = xmlutils(xblock) 137 | 138 | self.Title = xblock.findall('TitleBar')[0].text 139 | self.Subtitle = xblock.findall('SubTitleBar')[0].text 140 | 141 | self.SectionLabel = xmlblock.GetField('SectionLabel', '') 142 | 143 | self.CurrentLayout = xblock.findall('FrameLayout')[0].text 144 | 145 | self.LeftColumnProportion = int( xblock.findall('ColumnProportion')[0].text ) 146 | 147 | self.TitleMode = xblock.findall('TitleMode')[0].text 148 | 149 | columns = xblock.findall('Column') 150 | 151 | k = 0 152 | 153 | for column in columns: 154 | # get the blocks 155 | for xmlblock in column.findall('Block'): 156 | 157 | block = BeamerBlock() 158 | block.ReadXMLContent(xmlblock) 159 | 160 | self.Columns[k].append(block) 161 | 162 | k += 1 163 | 164 | 165 | # Build the internal elements 166 | 167 | def GenLaTeX(self): 168 | latexcontent = [] 169 | 170 | if self.TitleMode == "Section": 171 | latexcontent.append("\\section{" + self.SectionLabel + "}") 172 | 173 | if self.TitleMode == "Subsection": 174 | latexcontent.append("\\subsection{" + self.SectionLabel + "}") 175 | 176 | if self.CurrentLayout == "layout_title": 177 | latexcontent.append("\\begin{frame}" ) 178 | latexcontent.append("\\sectionText{" + self.Title + "}" ) 179 | latexcontent.append("\\end{frame}" ) 180 | return latexcontent 181 | 182 | 183 | latexcontent.append("\\begin{frame}{" + self.Title + "}") 184 | 185 | useColumns = False 186 | 187 | columnper = [100] 188 | 189 | if len(self.Columns[1]) > 0: 190 | useColumns = True 191 | # Add something to start the column environment 192 | latexcontent.append("\\begin{columns}[t]") 193 | 194 | framesize = 10 195 | 196 | leftcol = round (self.LeftColumnProportion*10/100) 197 | rightcol = framesize - leftcol 198 | 199 | leftcol = round( self.LeftColumnProportion/100, 2 ) 200 | 201 | rightcol= 1 - leftcol 202 | 203 | 204 | columnsizes = [leftcol-0.02, rightcol-0.02 ] 205 | 206 | columnper = [ self.LeftColumnProportion, (100-self.LeftColumnProportion) ] 207 | 208 | 209 | 210 | 211 | k = 0 212 | 213 | for column in self.Columns: 214 | 215 | # Add code to starting column 216 | if useColumns: 217 | latexcontent.append("\\column{"+str(columnsizes[k])+"\\linewidth}") 218 | 219 | for block in column: 220 | 221 | block.BlockWidth = columnper[k] 222 | 223 | blockLatex = block.GenLatex() 224 | latexcontent.extend(blockLatex) 225 | 226 | print("Adding block content: ") 227 | print(blockLatex) 228 | 229 | k += 1 230 | # Add code to ending column 231 | 232 | # Add code to ending column environment 233 | if useColumns: 234 | latexcontent.append("\\end{columns}") 235 | 236 | 237 | 238 | latexcontent.append("\\end{frame}") 239 | 240 | 241 | return latexcontent 242 | 243 | -------------------------------------------------------------------------------- /core/configFile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Fri Aug 2 12:58:24 2024 5 | 6 | @author: acroper 7 | """ 8 | 9 | import os 10 | 11 | import platform 12 | 13 | from pathlib import Path 14 | 15 | import configparser 16 | 17 | 18 | class Config: 19 | 20 | def __init__(self): 21 | 22 | self.ConfigFolder = "" 23 | 24 | self.DefineFolder() 25 | 26 | self.config = None 27 | 28 | self.StartConfig() 29 | 30 | 31 | def DefineFolder(self): 32 | 33 | LocalSystem = platform.system() 34 | 35 | if LocalSystem == "Linux" or LocalSystem == "Darwin": 36 | self.ConfigFolder = os.path.join(os.path.expanduser("~"), ".config/BeamerQt") 37 | 38 | if LocalSystem == "Windows": 39 | self.ConfigFolder = os.path.join( os.environ.get("APPDATA"), "BeamerQt") 40 | 41 | 42 | if not os.path.exists(self.ConfigFolder): 43 | os.makedirs(self.ConfigFolder) 44 | 45 | 46 | def StartConfig(self): 47 | 48 | self.configFile = os.path.join ( self.ConfigFolder, "config.ini") 49 | 50 | self.config = configparser.ConfigParser() 51 | 52 | if not os.path.exists(self.configFile): 53 | self.DefaultSettings() 54 | self.SaveConfig() 55 | 56 | self.config.read ( self.configFile ) 57 | 58 | 59 | def DefaultSettings(self): 60 | self.config.add_section("CustomThemes") 61 | self.config.add_section("Converters") 62 | 63 | self.config.add_section("SoftwarePath") 64 | 65 | self.config.set("Converters", "TEX-PDF", "pdflatex -interaction=nonstopmode $$i ") 66 | self.config.set("Converters", "SVG-PDF", "inkscape --file=$$i --export-area-drawing --without-gui --export-pdf=$$o ") 67 | 68 | 69 | def SaveConfig(self): 70 | with open(self.configFile,"w") as file_object: 71 | self.config.write(file_object) 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /core/frontMatter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import sys 20 | import os 21 | 22 | import xml.etree.ElementTree as ET 23 | from core.beamerBlock import * 24 | 25 | from core.xmlutils import * 26 | 27 | 28 | class frontMatter: 29 | 30 | def __init__(self): 31 | self.Title = "" 32 | self.ShortTitle = "" 33 | 34 | self.Subtitle = "" 35 | 36 | self.Author = "" 37 | self.ShortAuthor = "" 38 | 39 | self.Logo = None 40 | 41 | self.Background = None 42 | 43 | self.LogoPath = "" 44 | 45 | self.BackgroundPath = "" 46 | 47 | self.Options = "" 48 | 49 | self.Preamble = "" 50 | 51 | self.AspectRatio = "43" 52 | 53 | self.ShowSectionPage = "False" 54 | 55 | self.ShowSectionOutline = "False" 56 | 57 | self.OutlineTitle = "" 58 | 59 | self.getImageObjects() 60 | 61 | 62 | def getImageObjects(self): 63 | 64 | bb = BeamerBlock() 65 | 66 | self.Logo = bb.GetItemType("Image") 67 | self.Background = bb.GetItemType("Image") 68 | 69 | 70 | def GetXMLContent(self): 71 | ContentXML = ET.Element('FrontMatter') 72 | xmlblock = xmlutils(ContentXML) 73 | 74 | Title = ET.SubElement(ContentXML, 'Title') 75 | Title.text = self.Title 76 | 77 | 78 | 79 | Subtitle = ET.SubElement(ContentXML, 'Subtitle') 80 | Subtitle.text = self.Subtitle 81 | 82 | Author = ET.SubElement(ContentXML, 'Author') 83 | Author.text = self.Author 84 | 85 | Options = ET.SubElement(ContentXML, 'Options') 86 | Options.text = self.Options 87 | 88 | Logo = self.Logo.GetXMLContent() 89 | ContentXML.append(Logo) 90 | 91 | Background = self.Background.GetXMLContent() 92 | ContentXML.append(Background) 93 | 94 | xmlblock.SetField('Preamble', self.Preamble) 95 | xmlblock.SetField('LogoPath', self.LogoPath) 96 | xmlblock.SetField('BackgroundPath', self.BackgroundPath) 97 | 98 | xmlblock.SetField('ShortTitle', self.ShortTitle) 99 | xmlblock.SetField('ShortAuthor', self.ShortAuthor) 100 | 101 | xmlblock.SetField('AspectRatio', self.AspectRatio) 102 | 103 | xmlblock.SetField('ShowSectionPage', self.ShowSectionPage) 104 | xmlblock.SetField('ShowSectionOutline', self.ShowSectionOutline) 105 | xmlblock.SetField('OutlineTitle', self.OutlineTitle) 106 | 107 | self.ContentXML = ContentXML 108 | 109 | return ContentXML 110 | 111 | 112 | def ReadXMLContent(self, xblock): 113 | 114 | # self.Title = xblock.findall('Title')[0].text 115 | # self.Subtitle = xblock.findall('Subtitle')[0].text 116 | # self.Author = xblock.findall('Author')[0].text 117 | # self.Options = xblock.findall('Options')[0].text 118 | 119 | xmlblock = xmlutils(xblock) 120 | 121 | self.Title = xmlblock.GetField('Title', '') 122 | self.ShortTitle = xmlblock.GetField('ShortTitle', '') 123 | self.Subtitle = xmlblock.GetField('Subtitle', '') 124 | self.Author = xmlblock.GetField('Author', '') 125 | self.ShortAuthor = xmlblock.GetField('ShortAuthor', '') 126 | self.Options = xmlblock.GetField('Options', '') 127 | 128 | self.Preamble = xmlblock.GetField('Preamble', '') 129 | self.LogoPath = xmlblock.GetField('LogoPath', '') 130 | self.BackgroundPath = xmlblock.GetField('BackgroundPath', '') 131 | 132 | self.AspectRatio = xmlblock.GetField('AspectRatio', '43') 133 | 134 | self.ShowSectionPage = xmlblock.GetField('ShowSectionPage', '') 135 | self.ShowSectionOutline = xmlblock.GetField('ShowSectionOutline', '') 136 | self.OutlineTitle = xmlblock.GetField('OutlineTitle', '') 137 | 138 | self.Logo.ReadXMLContent(xblock.findall('ItemWidget')[0]) 139 | self.Background.ReadXMLContent(xblock.findall('ItemWidget')[1]) 140 | 141 | 142 | def GenLaTeXOptions(self): 143 | latexcontent = "\\documentclass[english" # ", aspectratio=169]{beamer}" 144 | if self.AspectRatio == "169": 145 | latexcontent = latexcontent + ", aspectratio=169" 146 | 147 | latexcontent = latexcontent + "]{beamer}\n" 148 | 149 | return latexcontent 150 | 151 | 152 | 153 | def GenLaTeX(self): 154 | latexcontent = [] 155 | 156 | # latexcontent.append(self.Preamble) 157 | 158 | latexcontent.append("\\title["+self.ShortTitle+"]{" + self.Title + "}") 159 | 160 | latexcontent.append("\\subtitle{" + self.Subtitle + "}") 161 | 162 | latexcontent.append("\\author["+self.ShortAuthor+"]{" + self.Author + "}") 163 | 164 | # latexcontent.append("\\makebeamertitle") 165 | 166 | # Sections 167 | 168 | sectionCode = [] 169 | 170 | if self.ShowSectionPage == "True": 171 | spage = "\\begin{frame} \n \\sectionText{\\secname} \n \\end{frame}" 172 | sectionCode.append(spage) 173 | 174 | if self.ShowSectionOutline == "True": 175 | spage = "\\frame{ \n \\frametitle{"+self.OutlineTitle+"} \\tableofcontents[currentsection] }" 176 | sectionCode.append(spage) 177 | 178 | if len(sectionCode) > 0: 179 | latexcontent.append( "\\AtBeginSection[]{" ) 180 | latexcontent.extend(sectionCode) 181 | latexcontent.append("}") 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | return latexcontent 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /core/preamble.tex: -------------------------------------------------------------------------------- 1 | %\documentclass[english, aspectratio=169]{beamer} 2 | \usepackage{lmodern} 3 | \renewcommand{\sfdefault}{lmss} 4 | \renewcommand{\ttdefault}{lmtt} 5 | \usepackage[T1]{fontenc} 6 | \usepackage[latin9]{inputenc} 7 | \usepackage{amssymb} 8 | \usepackage{graphicx} 9 | \usepackage{amsmath} 10 | 11 | 12 | \usepackage{animate} 13 | 14 | \usecolortheme{orchid} 15 | 16 | 17 | \usepackage{tikz} 18 | 19 | \usepackage{pgfgantt} 20 | \usetikzlibrary{shadows} 21 | \usetikzlibrary{shadings} 22 | 23 | \usepackage{seqsplit} 24 | 25 | 26 | \newcommand\sectionText[1]{ 27 | \begin{center} 28 | \begin{tikzpicture}[remember picture,overlay] 29 | \scalebox{1.5}{ 30 | \node[black!30!white, text width=7cm ] at (0.03,-0.19) { 31 | {\begin{center} \Huge{#1} \end{center}} 32 | }; 33 | \node [ text width=7cm] at (0,0) { 34 | {\begin{center} \Huge{#1} \end{center} } 35 | }; 36 | } 37 | \end{tikzpicture} 38 | \end{center} 39 | } 40 | 41 | 42 | \makeatletter 43 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Textclass specific LaTeX commands. 44 | % this default might be overridden by plain title style 45 | \newcommand\makebeamertitle{\frame{\maketitle}}% 46 | % (ERT) argument for the TOC 47 | \AtBeginDocument{% 48 | \let\origtableofcontents=\tableofcontents 49 | \def\tableofcontents{\@ifnextchar[{\origtableofcontents}{\gobbletableofcontents}} 50 | \def\gobbletableofcontents#1{\origtableofcontents} 51 | } 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /core/template.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import sys 20 | import os 21 | 22 | import xml.etree.ElementTree as ET 23 | 24 | 25 | class BeamerTemplate: 26 | 27 | def __init__(self): 28 | 29 | self.Name = "Warsaw" 30 | 31 | self.Preview = None 32 | 33 | self.UseTheme = "" 34 | 35 | self.CustomCode = "" 36 | 37 | 38 | def GetXMLContent(self): 39 | 40 | # Right now, just saving the template name 41 | # Future versions, it will copy the code of the 42 | # template to the file, so it will more portable 43 | TemplateXML = ET.Element('Template') 44 | TemplateXML.text = self.Name 45 | 46 | return TemplateXML 47 | 48 | 49 | def ReadXMLFile(self, file): 50 | tree = ET.parse(file) 51 | root = tree.getroot()[0] 52 | self.ReadXMLContent(root) 53 | 54 | 55 | def ReadXMLContent(self, xblock): 56 | self.SetTemplate( xblock.text ) 57 | 58 | 59 | 60 | def SetTemplate(self, name): 61 | self.Name = name 62 | 63 | # try to reach the template file 64 | tempfile = os.path.join("templates", self.Name.lower()+".xml") 65 | 66 | 67 | tree = ET.parse(tempfile) 68 | 69 | self.UseTheme = tree.findall('UseTheme')[0].text 70 | self.CustomCode = tree.findall('CustomCode')[0].text 71 | 72 | 73 | 74 | 75 | def GetPreview(self): 76 | # Try to generate a preview 77 | None 78 | 79 | 80 | def GenLaTeX(self): 81 | latexcontent = [] 82 | 83 | if self.UseTheme != "": 84 | latexcontent.append("\\usetheme{"+self.UseTheme+"}") 85 | 86 | if self.CustomCode != "": 87 | latexcontent.append(self.CustomCode) 88 | 89 | 90 | # latexcontent.append("\\begin{document}") 91 | 92 | 93 | return latexcontent 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /core/xmlutils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import sys 20 | import os 21 | 22 | import xml.etree.ElementTree as ET 23 | 24 | 25 | 26 | class xmlutils: 27 | 28 | def __init__(self, xblock): 29 | self.xblock = xblock 30 | 31 | 32 | def GetField(self, field, default): 33 | try: 34 | result = self.xblock.findall(field)[0].text 35 | except: 36 | result = default 37 | 38 | if result == None: 39 | result = default 40 | 41 | return result 42 | 43 | def SetField(self, field, value): 44 | 45 | LocalField = ET.SubElement(self.xblock, field) 46 | LocalField.text = value 47 | 48 | -------------------------------------------------------------------------------- /gui/About.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 552 10 | 642 11 | 12 | 13 | 14 | About BeamerQT 15 | 16 | 17 | 18 | icons/BQTIcon.pngicons/BQTIcon.png 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | icons/BQTIcon.png 30 | 31 | 32 | false 33 | 34 | 35 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | <html><head/><body><p><span style=" font-weight:600;">About BeamerQT</span></p><p>Version 0.1.4 </p><p><br/></p><p>Created by Jorge Guerrero.</p><p><a href="mailto:acroper@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">acroper@gmail.com</span></a></p><p><br/>Contribute to the development of this software</p><p><a href="https://www.paypal.com/donate/?business=2PP5H8Z8L5E8E&amp;no_recurring=0&amp;item_name=Support+the+development+of+BeamerQT&amp;currency_code=USD"><span style=" text-decoration: underline; color:#0000ff;">Donate via PayPal</span></a></p><p><br/></p><p><br/></p></body></html> 45 | 46 | 47 | Qt::RichText 48 | 49 | 50 | false 51 | 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 62 | 200 63 | 64 | 65 | 66 | 67 | 68 | 69 | icons/Donate_QR Code.png 70 | 71 | 72 | Qt::AlignCenter 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Qt::Horizontal 84 | 85 | 86 | QDialogButtonBox::Ok 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | buttonBox 96 | accepted() 97 | Dialog 98 | accept() 99 | 100 | 101 | 248 102 | 254 103 | 104 | 105 | 157 106 | 274 107 | 108 | 109 | 110 | 111 | buttonBox 112 | rejected() 113 | Dialog 114 | reject() 115 | 116 | 117 | 316 118 | 260 119 | 120 | 121 | 286 122 | 274 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /gui/AboutWidget.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore, QtGui 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject 28 | 29 | from core.template import * 30 | 31 | 32 | class AboutWidget(QtWidgets.QDialog): 33 | 34 | def __init__(self): 35 | 36 | super(AboutWidget, self).__init__() 37 | 38 | uic.loadUi('gui/About.ui', self) 39 | -------------------------------------------------------------------------------- /gui/ContentItem.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 500 10 | 403 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | QFrame::NoFrame 36 | 37 | 38 | QFrame::Plain 39 | 40 | 41 | 42 | 0 43 | 44 | 45 | 0 46 | 47 | 48 | 0 49 | 50 | 51 | 0 52 | 53 | 54 | 0 55 | 56 | 57 | 58 | 59 | 0 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 0 70 | 71 | 72 | QLayout::SetFixedSize 73 | 74 | 75 | 76 | 77 | Qt::Vertical 78 | 79 | 80 | QSizePolicy::Expanding 81 | 82 | 83 | 84 | 10 85 | 40 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 10 95 | 10 96 | 97 | 98 | 99 | ... 100 | 101 | 102 | 103 | icons/leftarrow.pngicons/leftarrow.png 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 10 112 | 10 113 | 114 | 115 | 116 | ... 117 | 118 | 119 | 120 | icons/rightarrow.pngicons/rightarrow.png 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 10 129 | 10 130 | 131 | 132 | 133 | ... 134 | 135 | 136 | 137 | icons/cancel.pngicons/cancel.png 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /gui/ContentItems/Image/ContentItemImage.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject, Qt 28 | from PyQt6.QtGui import QPixmap, QIcon 29 | 30 | from gui.ContentItems.Image.imagebrowse import * 31 | 32 | from PyQt6.QtCore import pyqtSignal, QObject 33 | 34 | import xml.etree.ElementTree as ET 35 | import pathlib 36 | import subprocess 37 | 38 | import threading 39 | 40 | import uuid 41 | 42 | from core.xmlutils import * 43 | 44 | 45 | class ImageWidget(QWidget): 46 | 47 | ImageClicked = pyqtSignal() 48 | 49 | def __init__(self, image_path, parent=None): 50 | super().__init__(parent) 51 | 52 | self.image_path = image_path 53 | self.image_label = QPushButton(self) 54 | 55 | # self.image_label.setText("Hello") 56 | self.max_image_size_percent = 0.5 57 | 58 | self.image_label.clicked.connect(self.showImageDLG) 59 | 60 | self.load_image() 61 | 62 | 63 | def showImageDLG(self): 64 | self.ImageClicked.emit() 65 | 66 | def load_pixmap(self, pixmap): 67 | self.image_label.setIcon(pixmap) 68 | 69 | 70 | def load_image(self): 71 | 72 | self.icon = QIcon(self.image_path) 73 | 74 | self.image_label.setIcon(self.icon) 75 | 76 | self.pixmap =QPixmap(self.image_path) 77 | # parent_size = self.parentWidget().parentWidget().size() 78 | 79 | # max_image_size = parent_size * self.max_image_size_percent 80 | max_image_size = QtCore.QSize(300,300) 81 | 82 | image_size = self.pixmap.size() 83 | 84 | scale_factor = min(max_image_size.width() / image_size.width(), 85 | max_image_size.height() / image_size.height()) 86 | 87 | self.image_label.setIconSize(image_size * scale_factor) 88 | 89 | # self.setFixedSize(image_size * scale_factor*1.1) 90 | 91 | 92 | 93 | 94 | 95 | # self.pixmap =QPixmap(self.image_path) # Use walrus operator for concise assignment 96 | # self.image_label.setPixmap(self.pixmap) 97 | # self.adjust_size() 98 | # print("Image loaded") 99 | 100 | 101 | def adjust_size(self): 102 | """ 103 | Adjusts the widget size to maintain the aspect ratio of the image 104 | and limit the maximum size based on a percentage of the parent size. 105 | """ 106 | self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) 107 | 108 | # Get the parent widget size 109 | parent_size = self.parentWidget().size() 110 | 111 | # Calculate the maximum allowed image size 112 | max_image_size = parent_size * self.max_image_size_percent 113 | 114 | # Load the image and get its actual size 115 | image_size = self.pixmap.size() 116 | 117 | # Calculate the scaling factor to fit within the maximum size while maintaining aspect ratio 118 | scale_factor = min(max_image_size.width() / image_size.width(), 119 | max_image_size.height() / image_size.height()) 120 | 121 | # Apply scaling to the image 122 | self.pixmap = self.pixmap.scaled(image_size * scale_factor, Qt.AspectRatioMode.KeepAspectRatio) 123 | self.image_label.setPixmap(self.pixmap) 124 | 125 | # Set the widget size based on the scaled image 126 | self.setFixedSize(self.pixmap.size()) 127 | 128 | 129 | 130 | class itemWidgetImage(QtWidgets.QWidget): 131 | 132 | def __init__(self): 133 | 134 | super(itemWidgetImage, self).__init__() 135 | 136 | uic.loadUi('gui/ContentItems/Image/ItemImage.ui', self) 137 | 138 | self.InnerObject = itemImage() 139 | 140 | 141 | 142 | self.initialImage = os.path.join( pathlib.Path(__file__).parent.resolve() , 'add-image.png') 143 | 144 | # initialImage = "/tmp/ToPrint/IMG_20230212_123510.jpg" 145 | 146 | self.Image = ImageWidget(self.initialImage, self) 147 | 148 | self.layout.addWidget(self.Image) 149 | 150 | self.Image.show() 151 | 152 | self.Image.ImageClicked.connect(self.showImageDLG) 153 | 154 | 155 | 156 | 157 | 158 | def showImageDLG(self): 159 | print("Image clicked") 160 | ImgBrw = ImageBrowse() 161 | 162 | ImgBrw.SetImageItem(self.InnerObject) 163 | 164 | res = ImgBrw.exec() 165 | if res: 166 | self.InnerObject.image_path = ImgBrw.image_path 167 | self.InnerObject.Width = ImgBrw.percentage 168 | self.InnerObject.pixmap = None 169 | self.Refresh(True) 170 | 171 | def GetInnerObject(self): 172 | # self.InnerObject.Text = self.TextEditor.toPlainText() 173 | self.InnerObject.PrevText = self.prevText.text() 174 | self.InnerObject.PostText = self.posText.text() 175 | return self.InnerObject 176 | 177 | def SetInnerObject(self, inner): 178 | self.InnerObject = inner 179 | self.Refresh() 180 | 181 | 182 | def Refresh(self, forced = False): 183 | # self.TextEditor.setText(self.InnerObject.Text) 184 | 185 | self.prevText.setText( self.InnerObject.PrevText ) 186 | self.posText.setText(self.InnerObject.PostText) 187 | 188 | if os.path.exists(self.InnerObject.image_path): 189 | self.Image.image_path = self.InnerObject.image_path 190 | 191 | 192 | if not forced and self.InnerObject.Pixmap != None: 193 | self.Image.load_pixmap(self.InnerObject.Pixmap) 194 | else: 195 | self.Image.load_image() 196 | self.InnerObject.Pixmap = self.Image.icon 197 | 198 | 199 | 200 | 201 | 202 | class itemImage(): 203 | 204 | # Once implemented, this module should store the files 205 | # inside the temporal folder 206 | 207 | 208 | def __init__(self): 209 | 210 | self.Type = "Image" 211 | 212 | self.Text = "" 213 | 214 | self.PrevText = "" 215 | 216 | self.PostText = "" 217 | 218 | self.Alignment = "Left" 219 | 220 | self.image_path = "" 221 | 222 | self.Pixmap = None # Used to store the image, and not load it everytime 223 | 224 | self.Width = 100 225 | 226 | self.uuid = str(uuid.uuid4()) 227 | 228 | 229 | 230 | 231 | def LoadPixmap(self): 232 | if os.path.exists(self.image_path ): 233 | # Try to load the image 234 | x = threading.Thread(target=self.LoadPixmapThread, args=(self,)) 235 | x.start() 236 | 237 | def LoadPixmapThread(self, arg): 238 | self.Pixmap =QIcon(self.image_path) 239 | 240 | 241 | def GetXMLContent(self): 242 | ContentXML = ET.Element('ItemWidget', ItemType='Image') 243 | ContentXML.text = self.Text 244 | 245 | ImagePath = ET.SubElement(ContentXML, "ImagePath") 246 | ImagePath.text = self.image_path 247 | 248 | Width = ET.SubElement(ContentXML, "Width") 249 | Width.text = str(self.Width) 250 | 251 | 252 | Uid = ET.SubElement(ContentXML, "UUID") 253 | Uid.text = self.uuid 254 | 255 | Alignment = ET.SubElement(ContentXML, "Alignment") 256 | Alignment.text = self.Alignment 257 | 258 | PrevText = ET.SubElement(ContentXML, "PrevText") 259 | PrevText.text = self.PrevText 260 | 261 | PostText = ET.SubElement(ContentXML, "PostText") 262 | PostText.text = self.PostText 263 | 264 | 265 | 266 | return ContentXML 267 | 268 | 269 | def ReadXMLContent(self, xblock): 270 | 271 | xmlblock = xmlutils(xblock) 272 | 273 | self.Text = xblock.text 274 | ImagePath = xblock.findall("ImagePath")[0] 275 | self.image_path = ImagePath.text 276 | 277 | 278 | if self.image_path == None: 279 | self.image_path = "" 280 | else: 281 | self.LoadPixmap() 282 | 283 | 284 | try: 285 | Width = xblock.findall("Width")[0].text 286 | self.Width = int(Width) 287 | except: 288 | None 289 | 290 | try: 291 | self.uuid = xblock.findall("UUID")[0].text 292 | except: 293 | None 294 | 295 | 296 | self.Alignment = xmlblock.GetField("Alignment", "Left") 297 | 298 | self.PrevText = xmlblock.GetField("PrevText", "") 299 | self.PostText = xmlblock.GetField("PostText", "") 300 | 301 | 302 | 303 | 304 | def VerifyImage(self, image_path): 305 | 306 | output_path = image_path 307 | 308 | if image_path.endswith(".svg"): 309 | # convert the image 310 | output_path = image_path + ".pdf" 311 | 312 | # if not os.path.exists(self.image_path): 313 | subprocess.call("inkscape --file="+image_path + " --export-area-drawing --without-gui --export-pdf=" + output_path , shell=True) 314 | 315 | 316 | 317 | 318 | return output_path 319 | 320 | 321 | 322 | 323 | 324 | def GenLatex(self): 325 | 326 | latexcontent = [] 327 | 328 | if os.path.exists(self.image_path): 329 | 330 | image_path = self.VerifyImage(self.image_path) 331 | 332 | widthText = 10 333 | widthText = self.MaxItemSize 334 | 335 | # width = round( widthText * self.Width / 100 , 1) 336 | width = round(self.Width/100, 2) 337 | 338 | if self.PrevText != "": 339 | latexcontent.append( self.PrevText + "\\"+"\\" ) 340 | 341 | # latexcontent.append("\\begin{center}") 342 | # latexcontent.append("\\includegraphics[width="+str(width)+"cm]{" + image_path +"}") 343 | latexcontent.append("\\includegraphics[width="+str(width)+"\\linewidth]{" + image_path +"}") 344 | # latexcontent.append("\\end{center}") 345 | 346 | if self.PostText != "": 347 | latexcontent.append( "\\"+"\\" + self.PostText ) 348 | 349 | return latexcontent 350 | 351 | 352 | -------------------------------------------------------------------------------- /gui/ContentItems/Image/ImageBrowse.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 563 10 | 464 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Filename: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Browse 33 | 34 | 35 | 36 | 37 | 38 | 39 | Size: 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 10 49 | 50 | 51 | 200 52 | 53 | 54 | 10 55 | 56 | 57 | 10 58 | 59 | 60 | 100 61 | 62 | 63 | 100 64 | 65 | 66 | Qt::Horizontal 67 | 68 | 69 | 10 70 | 71 | 72 | 73 | 74 | 75 | 76 | 100% 77 | 78 | 79 | Qt::AlignCenter 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Block Width 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 0 99 | 0 100 | 101 | 102 | 103 | Preview: 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 0 112 | 0 113 | 114 | 115 | 116 | QFrame::StyledPanel 117 | 118 | 119 | QFrame::Raised 120 | 121 | 122 | 123 | 124 | 125 | true 126 | 127 | 128 | 129 | 130 | 0 131 | 0 132 | 523 133 | 285 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | Qt::Horizontal 155 | 156 | 157 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | buttonBox 167 | accepted() 168 | Dialog 169 | accept() 170 | 171 | 172 | 248 173 | 254 174 | 175 | 176 | 157 177 | 274 178 | 179 | 180 | 181 | 182 | buttonBox 183 | rejected() 184 | Dialog 185 | reject() 186 | 187 | 188 | 316 189 | 260 190 | 191 | 192 | 286 193 | 274 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /gui/ContentItems/Image/ItemImage.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 489 10 | 278 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | 0 36 | 37 | 38 | 39 | 40 | 41 | 0 42 | 20 43 | 44 | 45 | 46 | 47 | 16777215 48 | 20 49 | 50 | 51 | 52 | Add text here... 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 0 61 | 0 62 | 63 | 64 | 65 | 66 | 0 67 | 100 68 | 69 | 70 | 71 | QFrame::StyledPanel 72 | 73 | 74 | QFrame::Raised 75 | 76 | 77 | 78 | 0 79 | 80 | 81 | 0 82 | 83 | 84 | 0 85 | 86 | 87 | 0 88 | 89 | 90 | 0 91 | 92 | 93 | 94 | 95 | 0 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 0 107 | 20 108 | 109 | 110 | 111 | 112 | 16777215 113 | 20 114 | 115 | 116 | 117 | 118 | 119 | 120 | Add text here... 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /gui/ContentItems/Image/add-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/ContentItems/Image/add-image.png -------------------------------------------------------------------------------- /gui/ContentItems/Image/imagebrowse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject, Qt 28 | from PyQt6.QtGui import * 29 | 30 | import xml.etree.ElementTree as ET 31 | import pathlib 32 | 33 | from gui.guidialogs import * 34 | 35 | 36 | class ImageBrowse(QtWidgets.QDialog): 37 | 38 | def __init__(self): 39 | 40 | super(ImageBrowse, self).__init__() 41 | 42 | uic.loadUi('gui/ContentItems/Image/ImageBrowse.ui', self) 43 | 44 | self.percentage = 50 45 | self.SizeSlider.valueChanged.connect(self.UpdatePercentage) 46 | 47 | self.BrowseButton.clicked.connect(self.BrowseImage) 48 | 49 | self.ImageItem = None 50 | 51 | 52 | 53 | def SetImageItem(self, imageitem): 54 | self.ImageItem = imageitem 55 | self.image_path = self.ImageItem.image_path 56 | 57 | self.percentage = self.ImageItem.Width 58 | self.SizeSlider.setValue(self.percentage) 59 | 60 | 61 | if os.path.exists(self.image_path): 62 | self.PathText.setText(self.image_path) 63 | self.LoadImage() 64 | 65 | 66 | 67 | 68 | 69 | 70 | def UpdatePercentage(self): 71 | self.percentage = self.SizeSlider.value() 72 | self.SizeLabel.setText( str(self.percentage) + "%" ) 73 | 74 | 75 | def BrowseImage(self): 76 | filename = openFileNameDialog(self, "", "Supported Imagen files (*.png *.jpg *.jpeg *.svg)") 77 | 78 | if os.path.exists(filename): 79 | # assign image to the elements 80 | self.image_path = filename 81 | self.LoadImage() 82 | 83 | self.PathText.setText(filename) 84 | 85 | def LoadImage(self): 86 | 87 | self.pixmap =QPixmap(self.image_path) 88 | 89 | image_size = self.pixmap.size() 90 | 91 | max_image_size = self.size() * 0.8 92 | 93 | scale_factor = min(max_image_size.width() / image_size.width(), 94 | max_image_size.height() / image_size.height()) 95 | 96 | self.pixmap = self.pixmap.scaled(image_size * scale_factor, Qt.AspectRatioMode.KeepAspectRatio) 97 | 98 | self.image_label.setPixmap(self.pixmap) 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /gui/ContentItems/RTF/ContentItemRTF.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject 28 | from PyQt6.QtGui import * 29 | 30 | from PyQt6.QtWidgets import QApplication, QMainWindow, QTextEdit, QToolBar, QFileDialog, QComboBox 31 | from PyQt6.QtGui import QFont, QTextCharFormat, QTextCursor, QTextListFormat, QAction 32 | 33 | 34 | import xml.etree.ElementTree as ET 35 | 36 | from core.xmlutils import * 37 | 38 | 39 | class itemWidgetRTF(QtWidgets.QWidget): 40 | 41 | def __init__(self): 42 | 43 | super(itemWidgetRTF, self).__init__() 44 | 45 | uic.loadUi('gui/ContentItems/RTF/ItemRTF.ui', self) 46 | 47 | self.InnerObject = itemRTF() 48 | 49 | self.AssignActions() 50 | 51 | 52 | def AssignActions(self): 53 | self.bold_action.clicked.connect(self.toggle_bold) 54 | self.italic_action.clicked.connect(self.toggle_italic) 55 | self.bullet_list_action.clicked.connect(self.toggle_bullet_list) 56 | self.numbered_list_action.clicked.connect(self.toggle_numbered_list) 57 | 58 | 59 | 60 | def GetInnerObject(self): 61 | # self.InnerObject.Text = self.TextEditor.toPlainText() 62 | # self.InnerObject.RTF = self.TextEditor.document() 63 | self.InnerObject.RTF.setMarkdown( self.TextEditor.toMarkdown() ) 64 | 65 | return self.InnerObject 66 | 67 | def SetInnerObject(self, inner): 68 | self.InnerObject = inner 69 | self.Refresh() 70 | 71 | def Refresh(self): 72 | # self.TextEditor.setText(self.InnerObject.Text) 73 | # self.TextEditor.setDocument(self.InnerObject.RTF) 74 | 75 | self.TextEditor.setMarkdown(self.InnerObject.RTF.toMarkdown()) 76 | 77 | 78 | 79 | def clearFormat(self): 80 | fmt = QTextCharFormat() 81 | fmt.setFontWeight(QFont.Weight.Normal) 82 | fmt.setFontItalic(False) 83 | 84 | self.TextEditor.setCurrentCharFormat(fmt) 85 | 86 | 87 | # ----------------------------------------------------------------------------- 88 | # This source code was created with assistance from ChatGPT (OpenAI o3-mini-high). 89 | # ----------------------------------------------------------------------------- 90 | # Functions 91 | # toggle_bold 92 | # toggle_italic 93 | # toggle_bullet_list 94 | # toggle_numbered_list 95 | # change_font_size 96 | 97 | def toggle_bold(self): 98 | cursor = self.TextEditor.textCursor() 99 | fmt = QTextCharFormat() 100 | current_format = cursor.charFormat() 101 | # Toggle bold: if already bold, remove it; otherwise, apply bold. 102 | if current_format.fontWeight() == QFont.Weight.Bold: 103 | fmt.setFontWeight(QFont.Weight.Normal) 104 | else: 105 | fmt.setFontWeight(QFont.Weight.Bold) 106 | cursor.mergeCharFormat(fmt) 107 | if not cursor.hasSelection(): 108 | self.TextEditor.setCurrentCharFormat(fmt) 109 | 110 | self.TextEditor.setFocus() 111 | 112 | 113 | def toggle_italic(self): 114 | cursor = self.TextEditor.textCursor() 115 | fmt = QTextCharFormat() 116 | current_format = cursor.charFormat() 117 | fmt.setFontItalic(not current_format.fontItalic()) 118 | cursor.mergeCharFormat(fmt) 119 | if not cursor.hasSelection(): 120 | self.TextEditor.setCurrentCharFormat(fmt) 121 | 122 | self.TextEditor.setFocus() 123 | 124 | def toggle_bullet_list(self): 125 | cursor = self.TextEditor.textCursor() 126 | block = cursor.block() 127 | current_list = block.textList() 128 | cursor.beginEditBlock() 129 | if current_list and current_list.format().style() == QTextListFormat.Style.ListDisc: 130 | # Remove list formatting. 131 | block_format = cursor.blockFormat() 132 | block_format.setObjectIndex(-1) 133 | cursor.setBlockFormat(block_format) 134 | else: 135 | # Apply bullet list formatting. 136 | list_format = QTextListFormat() 137 | list_format.setStyle(QTextListFormat.Style.ListDisc) 138 | cursor.createList(list_format) 139 | cursor.endEditBlock() 140 | 141 | self.TextEditor.setFocus() 142 | 143 | def toggle_numbered_list(self): 144 | cursor = self.TextEditor.textCursor() 145 | block = cursor.block() 146 | current_list = block.textList() 147 | cursor.beginEditBlock() 148 | if current_list and current_list.format().style() == QTextListFormat.Style.ListDecimal: 149 | block_format = cursor.blockFormat() 150 | block_format.setObjectIndex(-1) 151 | cursor.setBlockFormat(block_format) 152 | else: 153 | list_format = QTextListFormat() 154 | list_format.setStyle(QTextListFormat.Style.ListDecimal) 155 | cursor.createList(list_format) 156 | cursor.endEditBlock() 157 | 158 | self.TextEditor.setFocus() 159 | 160 | # def change_font_size(self): 161 | # # Called when the user changes the selection in the dropbox. 162 | # size_name = self.size_combobox.currentText() 163 | # latex_command = self.latex_size_mapping.get(size_name, "\\normalsize") 164 | # point_size = self.size_point_mapping.get(size_name, 12) 165 | # cursor = self.TextEditor.textCursor() 166 | # fmt = QTextCharFormat() 167 | # fmt.setFontPointSize(point_size) 168 | # # Store the LaTeX size command in the custom property. 169 | # fmt.setProperty(LATEX_SIZE_PROPERTY, latex_command) 170 | # cursor.mergeCharFormat(fmt) 171 | # if not cursor.hasSelection(): 172 | # self.TextEditor.setCurrentCharFormat(fmt) 173 | 174 | 175 | 176 | 177 | class itemRTF(): 178 | 179 | def __init__(self): 180 | 181 | self.Type = "RTF" 182 | 183 | self.Text = "" 184 | 185 | self.RTF = QTextDocument() 186 | 187 | self.Alignment = "Left" 188 | 189 | 190 | def GetXMLContent(self): 191 | 192 | self.Text = self.RTF.toMarkdown() 193 | 194 | 195 | ContentXML = ET.Element('ItemWidget', ItemType='RTF') 196 | 197 | 198 | ContentXML.text = self.Text 199 | 200 | Alignment = ET.SubElement(ContentXML, "Alignment") 201 | Alignment.text = self.Alignment 202 | 203 | 204 | 205 | return ContentXML 206 | 207 | 208 | 209 | def ReadXMLContent(self, xblock): 210 | 211 | xmlblock = xmlutils(xblock) 212 | 213 | self.Text = xblock.text 214 | 215 | self.RTF.setMarkdown(self.Text) 216 | 217 | self.Alignment = xmlblock.GetField("Alignment", "Left") 218 | 219 | 220 | def CheckLaTeX(self, text): 221 | # Try to check if there is some basic latex command 222 | result = False 223 | 224 | options = ["\\begin", "\\["] 225 | 226 | for option in options: 227 | if option in text: 228 | return True 229 | 230 | return result 231 | 232 | 233 | 234 | def GenLatex(self): 235 | 236 | latexcontent = [] 237 | 238 | outText = self.document_to_latex() 239 | 240 | 241 | 242 | # try: 243 | # if not self.CheckLaTeX(outText): 244 | # outText = self.Text.replace("\n", "\\"+"\\ \n") 245 | # except: 246 | # None 247 | 248 | outText += "\\phantom{-}" 249 | # latexcontent.append(self.Text) 250 | 251 | print(outText) 252 | 253 | latexcontent.append(outText) 254 | 255 | return latexcontent 256 | 257 | 258 | def document_to_latex(self): 259 | """ 260 | Traverse the QTextDocument and convert each block and its fragments to LaTeX, 261 | handling inline formatting (bold/italic) and list environments. 262 | """ 263 | doc = self.RTF 264 | latex_lines = [] 265 | previous_list = None 266 | block = doc.begin() 267 | 268 | openlist = "" 269 | 270 | while block.isValid(): 271 | current_list = block.textList() 272 | # Start a new list environment if needed. 273 | if current_list is not None and (previous_list is None or previous_list != current_list): 274 | style = current_list.format().style() 275 | if style == QTextListFormat.Style.ListDisc: 276 | if openlist != "": 277 | latex_lines.append("\\end{"+openlist+"}") 278 | openlist = "itemize" 279 | latex_lines.append("\\begin{itemize}") 280 | elif style == QTextListFormat.Style.ListDecimal: 281 | if openlist != "": 282 | latex_lines.append("\\end{"+openlist+"}") 283 | openlist = "enumerate" 284 | latex_lines.append("\\begin{enumerate}") 285 | # End the list environment when the list ends. 286 | elif current_list is None and previous_list is not None: 287 | style = previous_list.format().style() 288 | if style == QTextListFormat.Style.ListDisc: 289 | latex_lines.append("\\end{itemize}") 290 | openlist = "" 291 | elif style == QTextListFormat.Style.ListDecimal: 292 | latex_lines.append("\\end{enumerate}") 293 | openlist = "" 294 | previous_list = None 295 | 296 | block_text = self.process_block_fragments(block) 297 | if current_list is not None: 298 | latex_lines.append("\\item " + block_text) 299 | else: 300 | latex_lines.append(block_text + "\n") 301 | previous_list = current_list 302 | block = block.next() 303 | 304 | # Close any unclosed list environments. 305 | if previous_list is not None: 306 | style = previous_list.format().style() 307 | if style == QTextListFormat.Style.ListDisc: 308 | latex_lines.append("\\end{itemize}") 309 | elif style == QTextListFormat.Style.ListDecimal: 310 | latex_lines.append("\\end{enumerate}") 311 | return "\n".join(latex_lines) 312 | 313 | def process_block_fragments(self, block): 314 | """ 315 | Process each fragment in a block and convert inline formatting (including size) 316 | into corresponding LaTeX commands. 317 | """ 318 | fragments = [] 319 | it = block.begin() 320 | while not it.atEnd(): 321 | fragment = it.fragment() 322 | if fragment.isValid(): 323 | fragments.append(self.format_fragment(fragment.text(), fragment.charFormat())) 324 | it += 1 325 | return "".join(fragments) 326 | 327 | def format_fragment(self, text, char_format): 328 | """ 329 | Convert a text fragment with its formatting to LaTeX. Inline commands for bold and italic 330 | are applied, and if a custom LaTeX size is set (and is not the default \normalsize), 331 | the text is wrapped in a group with that size command. 332 | """ 333 | text = self.escape_latex(text) 334 | # First, wrap text for bold and italic. 335 | base = text 336 | bold = (char_format.fontWeight() == QFont.Weight.Bold) 337 | italic = char_format.fontItalic() 338 | if bold and italic: 339 | base = "\\textbf{\\textit{" + text + "}}" 340 | elif bold: 341 | base = "\\textbf{" + text + "}" 342 | elif italic: 343 | base = "\\textit{" + text + "}" 344 | 345 | return base 346 | 347 | # # Check for the custom LaTeX size property. 348 | # size_cmd = char_format.property(LATEX_SIZE_PROPERTY) 349 | # if size_cmd and size_cmd != "\\normalsize": 350 | # return "{" + size_cmd + " " + base + "}" 351 | # else: 352 | # return base 353 | 354 | def escape_latex(self, text): 355 | """ 356 | Escape special characters for LaTeX. 357 | """ 358 | return text 359 | replacements = { 360 | '\\': r'\textbackslash{}', 361 | '&': r'\&', 362 | '%': r'\%', 363 | '$': r'\$', 364 | '#': r'\#', 365 | '_': r'\_', 366 | '{': r'\{', 367 | '}': r'\}', 368 | '~': r'\textasciitilde{}', 369 | '^': r'\textasciicircum{}', 370 | } 371 | for char, replacement in replacements.items(): 372 | text = text.replace(char, replacement) 373 | return text 374 | 375 | 376 | 377 | 378 | -------------------------------------------------------------------------------- /gui/ContentItems/RTF/ItemRTF.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 713 10 | 342 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | 36 | 37 | ... 38 | 39 | 40 | 41 | ../../icons/format-bold.png../../icons/format-bold.png 42 | 43 | 44 | QToolButton::DelayedPopup 45 | 46 | 47 | true 48 | 49 | 50 | 51 | 52 | 53 | 54 | ... 55 | 56 | 57 | 58 | ../../icons/format-italic.png../../icons/format-italic.png 59 | 60 | 61 | true 62 | 63 | 64 | 65 | 66 | 67 | 68 | ... 69 | 70 | 71 | 72 | ../../icons/format-item.png../../icons/format-item.png 73 | 74 | 75 | true 76 | 77 | 78 | 79 | 80 | 81 | 82 | ... 83 | 84 | 85 | 86 | ../../icons/format_numbered.png../../icons/format_numbered.png 87 | 88 | 89 | true 90 | 91 | 92 | 93 | 94 | 95 | 96 | Qt::Horizontal 97 | 98 | 99 | 100 | 40 101 | 20 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 20 113 | 114 | 115 | 116 | QFrame::NoFrame 117 | 118 | 119 | QFrame::Plain 120 | 121 | 122 | QTextEdit::AutoBulletList 123 | 124 | 125 | true 126 | 127 | 128 | Add text here... 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /gui/ContentItems/Text/ContentItemText.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject 28 | 29 | import xml.etree.ElementTree as ET 30 | 31 | from core.xmlutils import * 32 | 33 | 34 | class itemWidgetText(QtWidgets.QWidget): 35 | 36 | def __init__(self): 37 | 38 | super(itemWidgetText, self).__init__() 39 | 40 | uic.loadUi('gui/ContentItems/Text/ItemText.ui', self) 41 | 42 | self.InnerObject = itemText() 43 | 44 | 45 | 46 | def GetInnerObject(self): 47 | self.InnerObject.Text = self.TextEditor.toPlainText() 48 | return self.InnerObject 49 | 50 | def SetInnerObject(self, inner): 51 | self.InnerObject = inner 52 | self.Refresh() 53 | 54 | def Refresh(self): 55 | self.TextEditor.setText(self.InnerObject.Text) 56 | 57 | 58 | 59 | class itemText(): 60 | 61 | def __init__(self): 62 | 63 | self.Type = "Text" 64 | 65 | self.Text = "" 66 | 67 | self.Alignment = "Left" 68 | 69 | 70 | def GetXMLContent(self): 71 | 72 | 73 | ContentXML = ET.Element('ItemWidget', ItemType='Text') 74 | 75 | ContentXML.text = self.Text 76 | 77 | Alignment = ET.SubElement(ContentXML, "Alignment") 78 | Alignment.text = self.Alignment 79 | 80 | 81 | 82 | return ContentXML 83 | 84 | 85 | 86 | def ReadXMLContent(self, xblock): 87 | 88 | xmlblock = xmlutils(xblock) 89 | 90 | self.Text = xblock.text 91 | 92 | self.Alignment = xmlblock.GetField("Alignment", "Left") 93 | 94 | 95 | def CheckLaTeX(self, text): 96 | # Try to check if there is some basic latex command 97 | result = False 98 | 99 | options = ["\\begin", "\\["] 100 | 101 | for option in options: 102 | if option in text: 103 | return True 104 | 105 | return result 106 | 107 | 108 | 109 | def GenLatex(self): 110 | 111 | latexcontent = [] 112 | 113 | outText = self.Text 114 | 115 | try: 116 | if not self.CheckLaTeX(self.Text): 117 | outText = self.Text.replace("\n", "\\"+"\\ \n") 118 | except: 119 | outText = " " 120 | 121 | outText += "\\phantom{-}" 122 | # latexcontent.append(self.Text) 123 | latexcontent.append(outText) 124 | 125 | return latexcontent 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /gui/ContentItems/Text/ItemText.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 502 10 | 329 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | 36 | 20 37 | 38 | 39 | 40 | QFrame::NoFrame 41 | 42 | 43 | QFrame::Plain 44 | 45 | 46 | false 47 | 48 | 49 | Add text here... 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /gui/DocumentTab.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 839 10 | 537 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | true 24 | 25 | 26 | true 27 | 28 | 29 | true 30 | 31 | 32 | 33 | Tab 1 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /gui/DualSlider.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | """ 19 | This module was created with the assistance of Google Gemini chat 20 | """ 21 | 22 | import sys 23 | from PyQt6.QtCore import Qt, QSize, QRect, pyqtSignal 24 | from PyQt6.QtGui import QPainter, QPen, QBrush, QFontMetrics, QPalette, QFont 25 | from PyQt6.QtWidgets import * 26 | 27 | class DualSlider(QWidget): 28 | 29 | ValueUpdated = pyqtSignal() 30 | Released = pyqtSignal() 31 | 32 | def __init__(self, parent=None): 33 | super().__init__(parent) 34 | self.minimum = 0 35 | self.maximum = 100 36 | self.value1 = 25 37 | self.value2 = 50 38 | self.value3 = 75 39 | self.handleWidth = 20 40 | self.handleHeight = 10 41 | 42 | self.selected1 = False 43 | self.selected2 = False 44 | self.selected3 = False 45 | 46 | self.Minimum = 10 47 | self.Maximum = 90 48 | 49 | self.ActiveSliders = 3 50 | 51 | self.Updating = False 52 | 53 | self.showNumbers = True 54 | 55 | self.NextRounding = 5 56 | 57 | self.LabelPosition = "Down" 58 | 59 | 60 | def UpdateValues(self, values): 61 | self.Updating = True 62 | 63 | if len(values) > 0: 64 | self.value1 = values[0] 65 | if len(values) > 1: 66 | self.value2 = values[1] 67 | if len(values) > 2: 68 | self.value3 = values[2] 69 | 70 | self.update() 71 | 72 | self.Updating = False 73 | 74 | 75 | def setSliders(self, number): 76 | self.ActiveSliders = number 77 | self.update() 78 | 79 | def adjustValues(self, PosValues): 80 | current = 0 81 | for k in range(len(PosValues)): 82 | if PosValues[k]-current < self.Minimum: 83 | PosValues[k] = current + self.Minimum 84 | current = PosValues[k] 85 | 86 | current = 100 87 | for k in range(len(PosValues)): 88 | if current-PosValues[-1-k] < self.Minimum: 89 | PosValues[-1-k] = current - self.Minimum 90 | current = PosValues[-1-k] 91 | return PosValues 92 | 93 | def verifyValues(self): 94 | # Check range of the values 95 | if self.ActiveSliders ==3: 96 | PosValues = [self.value1, self.value2, self.value3] 97 | if self.ActiveSliders == 2: 98 | PosValues = [self.value1, self.value2] 99 | if self.ActiveSliders == 1: 100 | PosValues = [self.value1] 101 | if self.ActiveSliders ==0: 102 | return 103 | 104 | PosValues = self.adjustValues(PosValues) 105 | if PosValues[0] < self.Minimum: 106 | PosValues[0] = self.Minimum 107 | if PosValues[-1] > self.Maximum: 108 | PosValues[-1] = self.Maximum 109 | 110 | if self.ActiveSliders > 0: 111 | self.value1 = int(PosValues[0]) 112 | 113 | if self.ActiveSliders > 1: 114 | self.value2 = int(PosValues[1]) 115 | 116 | if self.ActiveSliders > 2: 117 | self.value3 = int(PosValues[2]) 118 | 119 | if not self.Updating: 120 | self.ValueUpdated.emit() 121 | 122 | 123 | def extraRound(self): 124 | k = self.NextRounding 125 | self.value1 = k*round(self.value1/k) 126 | self.value2 = k*round(self.value2/k) 127 | self.value3 = k*round(self.value3/k) 128 | 129 | self.update() 130 | 131 | 132 | 133 | def paintEvent(self, event): 134 | self.verifyValues() 135 | painter = QPainter(self) 136 | painter.setRenderHint(QPainter.RenderHint.Antialiasing) 137 | 138 | # Draw the slider track 139 | rect = self.rect().adjusted(2, 2, -2, -2) 140 | painter.fillRect(rect, self.palette().brush(QPalette.ColorRole.Mid)) 141 | 142 | # Draw the slider handles 143 | self.handle1Rect = QRect( int( self.mapFromValue(self.value1-0.9)) , int( self.rect().y() ), self.handleWidth, self.handleHeight) 144 | self.handle2Rect = QRect(int(self.mapFromValue(self.value2-0.9)), int(self.rect().y()), self.handleWidth, self.handleHeight) 145 | self.handle3Rect = QRect(int(self.mapFromValue(self.value3-0.9)), int(self.rect().y()), self.handleWidth, self.handleHeight) 146 | 147 | handle1Rect = QRect( int( self.mapFromValue(self.value1)) , int( self.rect().y() ), 2, self.handleHeight) 148 | handle2Rect = QRect(int(self.mapFromValue(self.value2)), int(self.rect().y()), 2, self.handleHeight) 149 | handle3Rect = QRect(int(self.mapFromValue(self.value3)), int(self.rect().y()), 2, self.handleHeight) 150 | 151 | lineRect = QRect(int(self.mapFromValue(0)), 0, int(self.mapFromValue(100)), 1) 152 | 153 | 154 | # Draw the slider labels (optional) 155 | labelFont = QFont("Arial", 5) 156 | labelMetrics = QFontMetrics(labelFont) 157 | painter.setFont(labelFont) 158 | 159 | value1Label = str(self.value1) 160 | value2Label = str(self.value2) 161 | value3Label = str(self.value3) 162 | 163 | painter.fillRect(lineRect, self.palette().brush(QPalette.ColorRole.Dark)) 164 | 165 | adjustB = 0 166 | adjustS = 0 167 | 168 | if self.LabelPosition == "Right": 169 | adjustB = 9 170 | adjustS = -10 171 | 172 | 173 | if self.ActiveSliders > 0: 174 | painter.fillRect(handle1Rect, self.palette().brush(QPalette.ColorRole.Shadow)) 175 | label1Rect = QRect(handle1Rect.left() - 5 + adjustB, handle1Rect.bottom() + adjustS , 10, 10) 176 | if self.showNumbers: 177 | painter.drawText(label1Rect, Qt.AlignmentFlag.AlignCenter , value1Label) 178 | 179 | if self.ActiveSliders > 1: 180 | painter.fillRect(handle2Rect, self.palette().brush(QPalette.ColorRole.Shadow)) 181 | label2Rect = QRect(handle2Rect.left() - 5 + adjustB, handle2Rect.bottom() + adjustS, 10, 10) 182 | if self.showNumbers: 183 | painter.drawText(label2Rect, Qt.AlignmentFlag.AlignCenter, value2Label) 184 | 185 | if self.ActiveSliders > 2: 186 | painter.fillRect(handle3Rect, self.palette().brush(QPalette.ColorRole.Shadow)) 187 | label3Rect = QRect(handle3Rect.left() - 5 + adjustB, handle3Rect.bottom() + adjustS, 10, 10) 188 | if self.showNumbers: 189 | painter.drawText(label3Rect, Qt.AlignmentFlag.AlignCenter, value3Label) 190 | 191 | 192 | 193 | 194 | def mousePressEvent(self, event): 195 | if event.button() == Qt.MouseButton.LeftButton: 196 | if self.handle1Rect.contains(event.pos()) and not (self.selected2 or self.selected3): 197 | self.selected1 = True 198 | self.value1 = round(self.valueFromPosition(event.pos()), 0) 199 | # self.value2 = round(self.valueFromPosition(event.pos()), 0) 200 | self.update() 201 | 202 | if self.handle2Rect.contains(event.pos()) and not (self.selected1 or self.selected3): 203 | self.selected2 = True 204 | self.value2 = round(self.valueFromPosition(event.pos()), 0) 205 | # self.value2 = round(self.valueFromPosition(event.pos()), 0) 206 | self.update() 207 | 208 | if self.handle3Rect.contains(event.pos()) and not (self.selected1 or self.selected2): 209 | self.selected3 = True 210 | self.value3 = round(self.valueFromPosition(event.pos()), 0) 211 | # self.value2 = round(self.valueFromPosition(event.pos()), 0) 212 | self.update() 213 | 214 | def mouseMoveEvent(self, event): 215 | if event.buttons() & Qt.MouseButton.LeftButton: 216 | if self.rect().contains(event.pos()) and self.selected1: 217 | self.value1 = round(self.valueFromPosition(event.pos()), 0) 218 | # self.value2 = round(self.valueFromPosition(event.pos()), 0) 219 | self.update() 220 | 221 | if self.rect().contains(event.pos()) and self.selected2: 222 | self.value2 = round(self.valueFromPosition(event.pos()), 0) 223 | # self.value2 = round(self.valueFromPosition(event.pos()), 0) 224 | self.update() 225 | 226 | if self.rect().contains(event.pos()) and self.selected3: 227 | self.value3 = round(self.valueFromPosition(event.pos()), 0) 228 | # self.value2 = round(self.valueFromPosition(event.pos()), 0) 229 | self.update() 230 | 231 | def mouseReleaseEvent(self, event): 232 | self.selected1 = False 233 | self.selected2 = False 234 | self.selected3 = False 235 | self.extraRound() 236 | self.Released.emit() 237 | 238 | 239 | def sizeHint(self): 240 | return QSize(100, 30) 241 | 242 | def minimumSizeHint(self): 243 | return QSize(20, 20) 244 | 245 | def setMinimum(self, minimum): 246 | self.minimum = minimum 247 | self.update() 248 | 249 | def setMaximum(self, maximum): 250 | self.maximum = maximum 251 | self.update() 252 | 253 | def setValue1(self, value): 254 | self.value1 = value 255 | self.update() 256 | 257 | def setValue2(self, value): 258 | self.value2 = value 259 | self.update() 260 | 261 | def valueFromPosition(self, pos): 262 | return self.minimum + (pos.x() - self.rect().left()) * (self.maximum - self.minimum) / self.rect().width() 263 | 264 | 265 | def mapFromValue(self, value): 266 | return self.rect().left() + (value - self.minimum) * self.rect().width() / (self.maximum - self.minimum) 267 | 268 | 269 | # if __name__ == "__main__": 270 | # app = QApplication(sys.argv) 271 | # window = QWidget() 272 | # layout = QVBoxLayout() 273 | # slider = DualSlider() 274 | # layout.addWidget(slider) 275 | # window.setLayout(layout) 276 | # window.show() 277 | # sys.exit(app.exec()) -------------------------------------------------------------------------------- /gui/FrontMatter/FrontMatterWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1066 10 | 730 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | Qt::Horizontal 21 | 22 | 23 | true 24 | 25 | 26 | 10 27 | 28 | 29 | 30 | QFrame::StyledPanel 31 | 32 | 33 | QFrame::Raised 34 | 35 | 36 | 37 | 38 | 39 | 40 | 13 41 | 75 42 | true 43 | 44 | 45 | 46 | Front Matter Options 47 | 48 | 49 | Qt::PlainText 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Title: 59 | 60 | 61 | 62 | 63 | 64 | 65 | Presentation Title 66 | 67 | 68 | 69 | 70 | 71 | 72 | Short Title: 73 | 74 | 75 | 76 | 77 | 78 | 79 | Short Title 80 | 81 | 82 | 83 | 84 | 85 | 86 | Subtitle: 87 | 88 | 89 | 90 | 91 | 92 | 93 | Subtitle 94 | 95 | 96 | 97 | 98 | 99 | 100 | Author(s): 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 16777215 109 | 100 110 | 111 | 112 | 113 | Author names 114 | 115 | 116 | 117 | 118 | 119 | 120 | Short Author(s): 121 | 122 | 123 | 124 | 125 | 126 | 127 | Short Authors 128 | 129 | 130 | 131 | 132 | 133 | 134 | Logo: 135 | 136 | 137 | 138 | 139 | 140 | 141 | Path to the logo file - not implemented 142 | 143 | 144 | 145 | 146 | 147 | 148 | Background: 149 | 150 | 151 | 152 | 153 | 154 | 155 | Path to the background file - not implemented 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Qt::Horizontal 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 13 173 | 75 174 | true 175 | 176 | 177 | 178 | Presentation Options 179 | 180 | 181 | Qt::PlainText 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | Page Aspect: 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 4:3 200 | 201 | 202 | true 203 | 204 | 205 | 206 | 207 | 208 | 209 | 16:9 210 | 211 | 212 | 213 | 214 | 215 | 216 | Qt::Horizontal 217 | 218 | 219 | 220 | 40 221 | 20 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | Sectioning: 232 | 233 | 234 | 235 | 236 | 237 | 238 | Create title frame for section 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | Show the outline 248 | 249 | 250 | 251 | 252 | 253 | 254 | Outline Title 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 0 265 | 100 266 | 267 | 268 | 269 | Place your LaTeX code here... 270 | 271 | 272 | 273 | 274 | 275 | 276 | LaTeX preamble: 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 800 288 | 16777215 289 | 290 | 291 | 292 | QFrame::StyledPanel 293 | 294 | 295 | QFrame::Raised 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | Preview 304 | 305 | 306 | 307 | 308 | 309 | 310 | Qt::Horizontal 311 | 312 | 313 | 314 | 40 315 | 20 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | Refresh 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | -- Not implemented yet -- 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | - - Most functions not implemented yet - - 347 | 348 | 349 | 350 | 351 | 352 | 353 | Qt::Horizontal 354 | 355 | 356 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | buttonBox 366 | accepted() 367 | Dialog 368 | accept() 369 | 370 | 371 | 248 372 | 254 373 | 374 | 375 | 157 376 | 274 377 | 378 | 379 | 380 | 381 | buttonBox 382 | rejected() 383 | Dialog 384 | reject() 385 | 386 | 387 | 316 388 | 260 389 | 390 | 391 | 286 392 | 274 393 | 394 | 395 | 396 | 397 | 398 | -------------------------------------------------------------------------------- /gui/FrontMatter/Presets/Base.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/FrontMatter/Presets/Base.txt -------------------------------------------------------------------------------- /gui/FrontMatter/frontmatterwidget.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject 28 | 29 | 30 | from core.frontMatter import * 31 | 32 | 33 | 34 | 35 | class FrontMatterWidget(QtWidgets.QDialog): 36 | 37 | Selected = pyqtSignal() 38 | 39 | def __init__(self): 40 | 41 | super(FrontMatterWidget, self).__init__() 42 | 43 | uic.loadUi('gui/FrontMatter/FrontMatterWidget.ui', self) 44 | 45 | self.FrontMatter = frontMatter() 46 | 47 | 48 | def SetFrontMatter(self, front): 49 | 50 | self.FrontMatter = front 51 | self.LoadElements() 52 | 53 | 54 | def LoadElements(self): 55 | self.Title.setText(self.FrontMatter.Title) 56 | self.ShortTitle.setText(self.FrontMatter.ShortTitle) 57 | 58 | self.Subtitle.setText(self.FrontMatter.Subtitle) 59 | self.Authors.setPlainText(self.FrontMatter.Author) 60 | self.ShortAuthor.setText(self.FrontMatter.ShortAuthor) 61 | # self.Options.setPlainText(self.FrontMatter.Options) 62 | 63 | self.preambleText.setPlainText(self.FrontMatter.Preamble) 64 | 65 | self.Logo.setText(self.FrontMatter.LogoPath) 66 | self.Background.setText(self.FrontMatter.BackgroundPath) 67 | 68 | self.OutlineTitle.setText(self.FrontMatter.OutlineTitle) 69 | 70 | if self.FrontMatter.ShowSectionPage == "True": 71 | self.ShowSectionPage.setChecked(True) 72 | 73 | if self.FrontMatter.ShowSectionOutline == "True": 74 | self.ShowSectionOutline.setChecked(True) 75 | 76 | if self.FrontMatter.AspectRatio == "169": 77 | self.Aspect169.setChecked(True) 78 | 79 | 80 | def Save(self): 81 | self.FrontMatter.Title = self.Title.text() 82 | self.FrontMatter.ShortTitle = self.ShortTitle.text() 83 | self.FrontMatter.Subtitle = self.Subtitle.text() 84 | self.FrontMatter.Author = self.Authors.toPlainText() 85 | self.FrontMatter.ShortAuthor = self.ShortAuthor.text() 86 | # assign the logo locations 87 | # assign the background locations 88 | 89 | # self.FrontMatter.Options = self.Options.toPlainText() 90 | 91 | self.FrontMatter.Preamble = self.preambleText.toPlainText() 92 | 93 | self.FrontMatter.LogoPath = self.Logo.text() 94 | self.FrontMatter.BackgroundPath = self.Background.text() 95 | 96 | self.FrontMatter.OutlineTitle = self.OutlineTitle.text() 97 | 98 | if self.Aspect169.isChecked(): 99 | self.FrontMatter.AspectRatio = "169" 100 | else: 101 | self.FrontMatter.AspectRatio = "43" 102 | 103 | 104 | if self.ShowSectionPage.isChecked(): 105 | self.FrontMatter.ShowSectionPage = "True" 106 | else: 107 | self.FrontMatter.ShowSectionPage = "False" 108 | 109 | if self.ShowSectionOutline.isChecked(): 110 | self.FrontMatter.ShowSectionOutline = "True" 111 | else: 112 | self.FrontMatter.ShowSectionOutline = "False" 113 | 114 | 115 | # print(self.FrontMatter.Preamble) 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /gui/MainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1402 10 | 621 11 | 12 | 13 | 14 | Beamer QT 15 | 16 | 17 | 18 | icons/BQTIcon.pngicons/BQTIcon.png 19 | 20 | 21 | 22 | 23 | 0 24 | 25 | 26 | 0 27 | 28 | 29 | 0 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | 37 | 38 | 0 39 | 0 40 | 41 | 42 | 43 | QFrame::StyledPanel 44 | 45 | 46 | QFrame::Raised 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | QFrame::StyledPanel 56 | 57 | 58 | QFrame::Raised 59 | 60 | 61 | 62 | 63 | 64 | Qt::Horizontal 65 | 66 | 67 | 68 | 151 69 | 20 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 20 79 | 20 80 | 81 | 82 | 83 | - 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 150 92 | 20 93 | 94 | 95 | 96 | 97 | 150 98 | 20 99 | 100 | 101 | 102 | 20 103 | 104 | 105 | 200 106 | 107 | 108 | 10 109 | 110 | 111 | 100 112 | 113 | 114 | Qt::Horizontal 115 | 116 | 117 | false 118 | 119 | 120 | QSlider::TicksBelow 121 | 122 | 123 | 20 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 20 132 | 20 133 | 134 | 135 | 136 | + 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 40 145 | 0 146 | 147 | 148 | 149 | 150 | 16777215 151 | 20 152 | 153 | 154 | 155 | 100% 156 | 157 | 158 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 0 173 | 0 174 | 1402 175 | 29 176 | 177 | 178 | 179 | 180 | File 181 | 182 | 183 | 184 | Open recent 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | Help 203 | 204 | 205 | 206 | 207 | 208 | View 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | Slide 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 200 237 | 44 238 | 239 | 240 | 241 | 242 | 200 243 | 524287 244 | 245 | 246 | 247 | QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable 248 | 249 | 250 | Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea 251 | 252 | 253 | Slides 254 | 255 | 256 | 1 257 | 258 | 259 | 260 | 261 | 0 262 | 263 | 264 | 0 265 | 266 | 267 | 0 268 | 269 | 270 | 0 271 | 272 | 273 | 0 274 | 275 | 276 | 277 | 278 | 0 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 0 289 | 0 290 | 291 | 292 | 293 | QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable 294 | 295 | 296 | Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea 297 | 298 | 299 | Properties 300 | 301 | 302 | 2 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | toolBar 315 | 316 | 317 | TopToolBarArea 318 | 319 | 320 | false 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | New 337 | 338 | 339 | 340 | 341 | Open... 342 | 343 | 344 | 345 | 346 | Save 347 | 348 | 349 | 350 | 351 | About Beamer QT 352 | 353 | 354 | 355 | 356 | Save as 357 | 358 | 359 | 360 | 361 | Exit 362 | 363 | 364 | 365 | 366 | Zoom In 367 | 368 | 369 | 370 | 371 | Zoom Out 372 | 373 | 374 | 375 | 376 | Add new slide 377 | 378 | 379 | 380 | 381 | Reset slide number 382 | 383 | 384 | 385 | 386 | Reorder slides 387 | 388 | 389 | 390 | 391 | Generate LaTeX 392 | 393 | 394 | 395 | 396 | Configure Front Matter 397 | 398 | 399 | 400 | 401 | Previous files 402 | 403 | 404 | 405 | 406 | Duplicate slide 407 | 408 | 409 | 410 | 411 | LaTeX Folder 412 | 413 | 414 | 415 | 416 | Copy 417 | 418 | 419 | 420 | 421 | Paste 422 | 423 | 424 | 425 | 426 | Delete 427 | 428 | 429 | 430 | 431 | 432 | 433 | -------------------------------------------------------------------------------- /gui/RecentFiles.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import sys 20 | import os 21 | 22 | 23 | 24 | class RecentFiles: 25 | 26 | def __init__(self, config): 27 | # Load recent files from disk 28 | self.RecentList = [] 29 | 30 | self.RecentNames = [] 31 | 32 | self.HistoryFile = os.path.join ( config.ConfigFolder , "history.txt") 33 | 34 | self.LoadList() 35 | 36 | 37 | def LoadList(self): 38 | 39 | if os.path.exists(self.HistoryFile): 40 | hfile = open(self.HistoryFile, "r") 41 | self.RecentList = hfile.readlines() 42 | else: 43 | hfile = open(self.HistoryFile, "w") 44 | 45 | hfile.close() 46 | 47 | self.CheckList() 48 | 49 | def CheckList(self): 50 | tmplist = self.RecentList.copy() 51 | 52 | tmplist.reverse() 53 | self.RecentList.clear() 54 | 55 | for elemn in tmplist: 56 | 57 | if elemn not in ["", "\n"]: 58 | if elemn not in self.RecentList: 59 | elemn = elemn.replace("\n","") 60 | self.RecentList.append(elemn) 61 | 62 | self.RecentList.reverse() 63 | # reduce the list 64 | if len(self.RecentList) > 10: 65 | for k in range( len(self.RecentList) - 10 ) : 66 | self.RecentList.pop(0) 67 | 68 | 69 | 70 | def SaveRecent(self): 71 | hfile = open(self.HistoryFile, "w") 72 | 73 | for elemn in self.RecentList: 74 | hfile.write(elemn + "\n") 75 | 76 | hfile.close() 77 | 78 | def AppendFile(self, filename): 79 | 80 | self.RecentList.append(filename) 81 | self.CheckList() 82 | self.SaveRecent() 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /gui/Slide.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject, Qt, QMimeData 28 | from PyQt6.QtGui import QDrag, QPixmap 29 | 30 | import xml.etree.ElementTree as ET 31 | 32 | 33 | class Slide: 34 | 35 | def __init__(self): 36 | 37 | self.Preview = None 38 | 39 | self.Blocks = [] 40 | self.Columns = [] 41 | 42 | self.CurrentLayout = "layout_standard" 43 | 44 | self.Modified = False 45 | 46 | self.FrameXML = ET.Element('Frame', id='frame_0', visible='True') 47 | 48 | def setPreview(self, pixmap): 49 | self.Preview = pixmap 50 | self.Modified = True 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /gui/SlidePrev.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 170 10 | 111 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 133 22 | 97 23 | 24 | 25 | 26 | 27 | 170 28 | 120 29 | 30 | 31 | 32 | Form 33 | 34 | 35 | 36 | 37 | 38 | 39 | 0 40 | 41 | 42 | 0 43 | 44 | 45 | 0 46 | 47 | 48 | 0 49 | 50 | 51 | 0 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | QFrame::NoFrame 60 | 61 | 62 | QFrame::Sunken 63 | 64 | 65 | 4 66 | 67 | 68 | 0 69 | 70 | 71 | 72 | 3 73 | 74 | 75 | 0 76 | 77 | 78 | 4 79 | 80 | 81 | 4 82 | 83 | 84 | 4 85 | 86 | 87 | 88 | 89 | 90 | 25 91 | 0 92 | 93 | 94 | 95 | 96 | 25 97 | 16777215 98 | 99 | 100 | 101 | 102 | 9 103 | 104 | 105 | 106 | 000 107 | 108 | 109 | Qt::PlainText 110 | 111 | 112 | false 113 | 114 | 115 | Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing 116 | 117 | 118 | 119 | 120 | 121 | 122 | QFrame::Box 123 | 124 | 125 | 2 126 | 127 | 128 | 129 | 130 | 131 | icons/base.png 132 | 133 | 134 | true 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 16777215 146 | 17 147 | 148 | 149 | 150 | 151 | 12 152 | false 153 | 154 | 155 | 156 | --- 157 | 158 | 159 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 160 | 161 | 162 | false 163 | 164 | 165 | 1 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /gui/SlideWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 958 10 | 724 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | background-color: rgb(175, 175, 175); 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 0 34 | 35 | 36 | 37 | 38 | 0 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /gui/Slidebar.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 177 10 | 757 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 31 | 32 | true 33 | 34 | 35 | QAbstractItemView::InternalMove 36 | 37 | 38 | Qt::MoveAction 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /gui/Test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | True 6 | Contenido 7 | 8 | 9 | 10 | 11 | True 12 | Contenido 13 | 14 | 15 | -------------------------------------------------------------------------------- /gui/ThumbListWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt6 import QtWidgets 2 | from PyQt6 import QtCore 3 | from PyQt6.QtWidgets import (QListWidget) 4 | 5 | class ThumbListWidget(QListWidget): 6 | 7 | def __init__(self, parent=None): 8 | super(ThumbListWidget, self).__init__(parent) 9 | self.setIconSize(QtCore.QSize(200, 200)) 10 | self.setDragDropMode(QtWidgets.QAbstractItemView.DragDropMode.InternalMove) 11 | 12 | self.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection) 13 | 14 | def dragEnterEvent(self, event): 15 | if event.mimeData().hasUrls(): 16 | event.accept() 17 | else: 18 | super(ThumbListWidget, self).dragEnterEvent(event) 19 | 20 | def dragMoveEvent(self, event): 21 | if event.mimeData().hasUrls(): 22 | event.setDropAction(QtCore.Qt.MoveAction) 23 | event.accept() 24 | else: 25 | super(ThumbListWidget, self).dragMoveEvent(event) 26 | 27 | def dropEvent(self, event): 28 | if event.mimeData().hasUrls(): 29 | event.setDropAction(QtCore.Qt.MoveAction) 30 | event.accept() 31 | links = [] 32 | for url in event.mimeData().urls(): 33 | links.append(str(url.toLocalFile())) 34 | else: 35 | # event.setDropAction(QtCore.Qt.MoveAction) 36 | super(ThumbListWidget, self).dropEvent(event) 37 | -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | BeamerQT 3 | Copyright (C) 2024 Mosquera Lab - Montana State University 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | -------------------------------------------------------------------------------- /gui/configurator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore, QtGui 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject 28 | 29 | from core.template import * 30 | 31 | 32 | class ConfiguratorWidget(QtWidgets.QWidget): 33 | 34 | def __init__(self): 35 | 36 | super(ConfiguratorWidget, self).__init__() 37 | 38 | uic.loadUi('gui/Configurator.ui', self) 39 | 40 | self.SelectedBlock = None 41 | 42 | self.LeftColumnValue = 100 43 | 44 | self.ThemeList = [] 45 | 46 | self.LoadActions() 47 | 48 | self.RefreshThemeList() 49 | 50 | self.Document = None 51 | 52 | 53 | 54 | # self.show() 55 | 56 | 57 | def SetDocument(self, documento): 58 | self.Document = documento 59 | 60 | # Check template 61 | tempName = self.Document.Template.Name 62 | 63 | self.themeBox.setCurrentText(tempName) 64 | 65 | 66 | def UpdateTheme(self): 67 | 68 | index = self.themeBox.currentIndex() 69 | self.Document.Template = self.ThemeList[index] 70 | 71 | 72 | 73 | 74 | def RefreshThemeList(self): 75 | 76 | # check in template folder 77 | filelist = os.listdir("templates") 78 | for file in filelist: 79 | if file.endswith("xml"): 80 | filename = os.path.join("templates", file ) 81 | 82 | if os.path.exists(filename): 83 | 84 | print("Opening file " + filename) 85 | 86 | 87 | templ = BeamerTemplate() 88 | templ.ReadXMLFile( filename ) 89 | self.themeBox.addItem(templ.Name) 90 | self.ThemeList.append(templ) 91 | 92 | 93 | self.themeBox.currentIndexChanged.connect(self.UpdateTheme) 94 | 95 | 96 | 97 | 98 | def ConnectFrame(self, NewFrame): 99 | self.CurrentFrame = NewFrame 100 | 101 | self.CurrentFrame.BlockSelected.connect(self.SelectBlock) 102 | 103 | self.CurrentFrame.Updated.connect(self.Refresh) 104 | 105 | 106 | # Actions for reposition the blocks. The signals will be sent to 107 | # the Frame directly 108 | # self.Left.clicked.connect(self.CurrentFrame.MoveLeft) 109 | # self.Right.clicked.connect(self.CurrentFrame.MoveRight) 110 | # self.Up.clicked.connect(self.CurrentFrame.MoveUp) 111 | # self.Down.clicked.connect(self.CurrentFrame.MoveDown) 112 | 113 | 114 | 115 | 116 | def SelectBlock(self): 117 | return 118 | # self.SelectedBlock = self.CurrentFrame.SelectedBlock 119 | # self.blockLayout.setVisible(True) 120 | 121 | # self.toolBox.setCurrentIndex(2) 122 | def UnSelectBlock(self): 123 | return 124 | # self.blockLayout.setVisible(False) 125 | 126 | 127 | def LoadActions(self): 128 | # Most actions are passed directly to the Frame object 129 | # self.show_title.stateChanged.connect(self.titlechange) 130 | # self.show_subtitle.stateChanged.connect(self.subtitlechange) 131 | 132 | self.layout_standard.toggled.connect(lambda: self.layoutchange("layout_standard")) 133 | self.layout_2cols.toggled.connect(lambda: self.layoutchange("layout_2cols")) 134 | self.layout_2rows.toggled.connect(lambda: self.layoutchange("layout_2rows")) 135 | self.layout_1col2rows.toggled.connect(lambda: self.layoutchange("layout_1col2rows")) 136 | self.layout_2rows1col.toggled.connect(lambda: self.layoutchange("layout_2rows1col")) 137 | self.layout_4blocks.toggled.connect(lambda: self.layoutchange("layout_4blocks")) 138 | self.layout_title.toggled.connect(lambda: self.layoutchange("layout_title")) 139 | self.layout_restore.toggled.connect(self.restoreLayout) 140 | 141 | # self.LeftColumnSize.valueChanged.connect(self.UpdateLeftColumn) 142 | # self.RightColumnSize.valueChanged.connect(self.UpdateRightColumn) 143 | 144 | 145 | 146 | 147 | def UpdateLeftColumn(self): 148 | return 149 | 150 | self.LeftColumnValue = self.LeftColumnSize.value() 151 | self.RightColumnText.setText( str(100-self.LeftColumnValue) ) 152 | # self.RightColumnSize.setValue(100-self.LeftColumnValue) 153 | 154 | 155 | if self.CurrentFrame.LeftColumnProportion != self.LeftColumnValue: 156 | self.CurrentFrame.LeftColumnProportion = self.LeftColumnValue 157 | self.CurrentFrame.updateColumnSize() 158 | 159 | 160 | def UpdateRightColumn(self): 161 | 162 | self.LeftColumnValue = 100 - self.RightColumnSize.value() 163 | self.LeftColumnSize.setValue(self.LeftColumnValue) 164 | self.CurrentFrame.LeftColumnProportion = self.LeftColumnValue 165 | # self.CurrentFrame.updateColumnSize() 166 | 167 | 168 | 169 | 170 | 171 | def restoreLayout(self): 172 | if self.CurrentFrame.Updating == False and self.layout_restore.isChecked() : 173 | button = QMessageBox.question(self, "Reset layout", "This will remove all blocks. \n\nContinue?") 174 | if button == QMessageBox.StandardButton.Yes: 175 | self.layoutchange("layout_restore") 176 | 177 | self.Refresh() 178 | 179 | 180 | def titlechange(self): 181 | self.CurrentFrame.config_title(self.show_title.isChecked()) 182 | 183 | 184 | def subtitlechange(self): 185 | None 186 | 187 | def layoutchange(self, option): 188 | self.CurrentFrame.config_Layout(option) 189 | 190 | def Refresh(self): 191 | # recheck the layout button 192 | 193 | self.CurrentFrame.Updating = True 194 | 195 | layoutstyle = self.CurrentFrame.CurrentLayout 196 | 197 | if layoutstyle == "layout_standard": 198 | self.layout_standard.setChecked(True) 199 | 200 | if layoutstyle == "layout_2cols": 201 | self.layout_2cols.setChecked(True) 202 | 203 | if layoutstyle == "layout_2rows": 204 | self.layout_2rows.setChecked(True) 205 | 206 | if layoutstyle == "layout_1col2rows": 207 | self.layout_1col2rows.setChecked(True) 208 | 209 | if layoutstyle == "layout_2rows1col": 210 | self.layout_2rows1col.setChecked(True) 211 | 212 | if layoutstyle == "layout_4blocks": 213 | self.layout_4blocks.setChecked(True) 214 | 215 | if layoutstyle == "layout_title": 216 | self.layout_title.setChecked(True) 217 | 218 | if layoutstyle == "Custom": 219 | self.layout_restore.setChecked(True) 220 | # self.layout_restore.setChecked(False) 221 | 222 | self.CurrentFrame.Updating = False 223 | 224 | self.UnSelectBlock() 225 | 226 | # self.LeftColumnSize.setValue(self.CurrentFrame.LeftColumnProportion) 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /gui/contentitem.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | 25 | from PyQt6 import QtWidgets, uic, QtCore 26 | from PyQt6.QtWidgets import * 27 | from PyQt6.QtCore import pyqtSignal, QObject 28 | from PyQt6.QtGui import QClipboard 29 | 30 | 31 | import xml.etree.ElementTree as ET 32 | 33 | from core.beamerBlock import * 34 | 35 | import importlib 36 | 37 | 38 | class ContentItem(QtWidgets.QWidget): 39 | 40 | Activated = pyqtSignal() 41 | 42 | def __init__(self): 43 | 44 | super(ContentItem, self).__init__() 45 | 46 | uic.loadUi('gui/ContentItem2.ui', self) 47 | 48 | self.ItemType = "Text" 49 | 50 | self.CurrentAction = "" 51 | 52 | self.InnerWidget = None 53 | 54 | self.SetActions() 55 | 56 | self.Alignment = "Default" 57 | 58 | self.Aligning = False 59 | 60 | 61 | def SetActions(self): 62 | 63 | self.prevBtn.clicked.connect(lambda: self.SetOption("prev")) 64 | self.nextBtn.clicked.connect(lambda: self.SetOption("next")) 65 | self.deleteBtn.clicked.connect(lambda: self.SetOption("delete")) 66 | 67 | self.AlignLeftBtn.clicked.connect(lambda: self.SetAlignment("Left")) 68 | self.AlignRightBtn.clicked.connect(lambda: self.SetAlignment("Right")) 69 | self.AlignCenterBtn.clicked.connect(lambda: self.SetAlignment("Center")) 70 | self.AlignDefaultBtn.clicked.connect(lambda: self.SetAlignment("Default")) 71 | 72 | toct = QWidgetAction(self) 73 | toct.setDefaultWidget(self.ButtonsWidget) 74 | self.opcButton.addAction(toct) 75 | 76 | 77 | 78 | 79 | def SetAlignment(self, option): 80 | self.Alignment = option 81 | 82 | if self.Aligning == False: 83 | self.CheckAlignment() 84 | 85 | 86 | def CheckAlignment(self): 87 | self.Aligning = True 88 | 89 | self.AlignCenterBtn.setChecked(False) 90 | self.AlignLeftBtn.setChecked(False) 91 | self.AlignRightBtn.setChecked(False) 92 | self.AlignDefaultBtn.setChecked(False) 93 | 94 | 95 | if self.Alignment == "Center": 96 | self.AlignCenterBtn.setChecked(True) 97 | self.opcButton.setIcon(self.AlignCenterBtn.icon()) 98 | 99 | if self.Alignment == "Left": 100 | self.AlignLeftBtn.setChecked(True) 101 | self.opcButton.setIcon(self.AlignLeftBtn.icon()) 102 | 103 | if self.Alignment == "Right": 104 | self.AlignRightBtn.setChecked(True) 105 | self.opcButton.setIcon(self.AlignRightBtn.icon()) 106 | 107 | if self.Alignment == "Default": 108 | self.AlignDefaultBtn.setChecked(True) 109 | self.opcButton.setIcon(self.AlignDefaultBtn.icon()) 110 | 111 | self.InnerWidget.GetInnerObject().Alignment = self.Alignment 112 | 113 | self.Aligning = False 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | def SetOption(self, option): 125 | self.CurrentAction = option 126 | self.Activated.emit() 127 | 128 | 129 | 130 | 131 | def setItemType(self, itemtype): 132 | # search the itemtype 133 | 134 | typeloc = 'gui.ContentItems.'+itemtype+ ".ContentItem"+itemtype 135 | 136 | 137 | module = importlib.import_module(typeloc) 138 | itemClass = getattr(module, "itemWidget" + itemtype ) 139 | Item = itemClass() 140 | 141 | Item.ContentItem = self 142 | 143 | self.Layout.addWidget(Item) 144 | 145 | self.InnerWidget = Item 146 | 147 | 148 | 149 | 150 | def GetInnerObject(self): 151 | 152 | return self.InnerWidget.GetInnerObject() 153 | 154 | def SetInnerObject(self, obj): 155 | 156 | self.InnerWidget.SetInnerObject(obj) 157 | 158 | self.Alignment = self.InnerWidget.GetInnerObject().Alignment 159 | 160 | # print(self.Alignment) 161 | 162 | self.Aligning = False 163 | 164 | self.CheckAlignment() 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /gui/framexml.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import xml.etree.ElementTree as ET 4 | 5 | FrameXML = ET.Element('Frame', id='frame_0') 6 | 7 | 8 | TitleBar = ET.SubElement(FrameXML, 'TitleBar') 9 | Titlebar_Visible = ET.SubElement(TitleBar, 'Visible', id='1') 10 | Titlebar_Text = ET.SubElement(TitleBar, 'Content') 11 | 12 | Titlebar_Visible.text = 'True' 13 | Titlebar_Text.text = 'Contenido' 14 | 15 | 16 | 17 | 18 | FrameXML2 = ET.Element('Frame', id='frame_1') 19 | 20 | 21 | TitleBar2 = ET.SubElement(FrameXML2, 'TitleBar') 22 | Titlebar_Visible2 = ET.SubElement(TitleBar2, 'Visible', id='2') 23 | Titlebar_Text2 = ET.SubElement(TitleBar2, 'Content') 24 | 25 | Titlebar_Visible2.text = 'True' 26 | Titlebar_Text2.text = 'Contenido' 27 | 28 | 29 | 30 | Documento = ET.Element('BeamerDoc') 31 | 32 | Documento.append(FrameXML) 33 | Documento.append(FrameXML2) 34 | 35 | 36 | 37 | tree = ET.ElementTree(Documento) 38 | ET.indent(tree, ' ') 39 | 40 | tree.write("Test.xml", encoding="utf-8", xml_declaration=True) 41 | 42 | print(tree) 43 | 44 | 45 | -------------------------------------------------------------------------------- /gui/guidialogs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import sys 20 | import os 21 | 22 | 23 | from PyQt6 import QtWidgets, uic, QtCore 24 | from PyQt6.QtWidgets import * 25 | from PyQt6.QtCore import pyqtSignal, QObject 26 | 27 | 28 | 29 | def openFileNameDialog(parent, location="", Filter = "All files (*.*)"): 30 | title = "Select a file" 31 | 32 | fileName = QFileDialog.getOpenFileName(parent, title, location, Filter) 33 | 34 | 35 | if fileName: 36 | return fileName[0] 37 | else: 38 | return "" 39 | 40 | 41 | def saveFileNameDialog(parent, location="", Filter = "All files (*.*)"): 42 | title = "Select a file" 43 | 44 | fileName = QFileDialog.getSaveFileName(parent, title, location, Filter) 45 | 46 | if fileName: 47 | return fileName[0] 48 | else: 49 | return "" 50 | 51 | def openFolderNameDialog(self, location): 52 | title = "Select a folder" 53 | options = QFileDialog.Options() 54 | options |= QFileDialog.DontUseNativeDialog 55 | fileName = QFileDialog.getExistingDirectory(parent, title, location) 56 | if fileName: 57 | return fileName 58 | else: 59 | return "" 60 | -------------------------------------------------------------------------------- /gui/icons/BQTIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/BQTIcon.png -------------------------------------------------------------------------------- /gui/icons/Donate_QR Code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/Donate_QR Code.png -------------------------------------------------------------------------------- /gui/icons/add-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/add-image.png -------------------------------------------------------------------------------- /gui/icons/align-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/align-center.png -------------------------------------------------------------------------------- /gui/icons/align-justified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/align-justified.png -------------------------------------------------------------------------------- /gui/icons/align-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/align-left.png -------------------------------------------------------------------------------- /gui/icons/align-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/align-right.png -------------------------------------------------------------------------------- /gui/icons/align_center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/align_center.png -------------------------------------------------------------------------------- /gui/icons/align_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/align_default.png -------------------------------------------------------------------------------- /gui/icons/align_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/align_left.png -------------------------------------------------------------------------------- /gui/icons/align_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/align_right.png -------------------------------------------------------------------------------- /gui/icons/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/arrow-down.png -------------------------------------------------------------------------------- /gui/icons/arrow-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/arrow-left.png -------------------------------------------------------------------------------- /gui/icons/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/arrow-right.png -------------------------------------------------------------------------------- /gui/icons/arrow-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/arrow-up.png -------------------------------------------------------------------------------- /gui/icons/base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/base.png -------------------------------------------------------------------------------- /gui/icons/blocks_column.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/blocks_column.png -------------------------------------------------------------------------------- /gui/icons/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/cancel.png -------------------------------------------------------------------------------- /gui/icons/clipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/clipboard.png -------------------------------------------------------------------------------- /gui/icons/column_blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/column_blocks.png -------------------------------------------------------------------------------- /gui/icons/downarrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/downarrow.png -------------------------------------------------------------------------------- /gui/icons/format-bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/format-bold.png -------------------------------------------------------------------------------- /gui/icons/format-italic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/format-italic.png -------------------------------------------------------------------------------- /gui/icons/format-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/format-item.png -------------------------------------------------------------------------------- /gui/icons/format_numbered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/format_numbered.png -------------------------------------------------------------------------------- /gui/icons/four_blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/four_blocks.png -------------------------------------------------------------------------------- /gui/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/icon.png -------------------------------------------------------------------------------- /gui/icons/leftarrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/leftarrow.png -------------------------------------------------------------------------------- /gui/icons/newBlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/newBlock.png -------------------------------------------------------------------------------- /gui/icons/restore_blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/restore_blocks.png -------------------------------------------------------------------------------- /gui/icons/rightarrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/rightarrow.png -------------------------------------------------------------------------------- /gui/icons/standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/standard.png -------------------------------------------------------------------------------- /gui/icons/title_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/title_frame.png -------------------------------------------------------------------------------- /gui/icons/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/trash.png -------------------------------------------------------------------------------- /gui/icons/two_columns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/two_columns.png -------------------------------------------------------------------------------- /gui/icons/two_rows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/two_rows.png -------------------------------------------------------------------------------- /gui/icons/uparrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/gui/icons/uparrow.png -------------------------------------------------------------------------------- /gui/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gui/slidewidget.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | import sys 21 | import os 22 | 23 | 24 | from PyQt6 import QtWidgets, uic, QtCore, QtGui 25 | from PyQt6.QtWidgets import * 26 | from PyQt6.QtCore import pyqtSignal, QObject, Qt 27 | from PyQt6.QtGui import QAction 28 | from gui.framewidget import * 29 | 30 | class SlideWidget(QtWidgets.QWidget): 31 | 32 | UpdatedZoom = pyqtSignal() 33 | 34 | def __init__(self): 35 | 36 | super(SlideWidget, self).__init__() 37 | 38 | uic.loadUi('gui/SlideWidget.ui', self) 39 | 40 | self.ZoomFactor = 1 41 | 42 | self.createScene() 43 | 44 | self.show() 45 | 46 | self.ControlDown = False 47 | self.ReptitionControl = 0 48 | 49 | 50 | def createScene(self): 51 | self._scene = QtWidgets.QGraphicsScene(self) 52 | self._view = QtWidgets.QGraphicsView(self._scene) 53 | 54 | self.CurrentFrame = FrameWidget() 55 | 56 | # texto = QLabel("Test") 57 | 58 | self._scene.addWidget(self.CurrentFrame) 59 | self.CurrentFrame.show() 60 | # self._scene.addWidget(texto) 61 | 62 | self.layout.addWidget(self._view) 63 | 64 | 65 | def keyPressEvent(self, event): 66 | key = event.key() 67 | if key == 16777249: 68 | self.ControlDown = True 69 | 70 | def keyReleaseEvent(self, event): 71 | self.ControlDown = False 72 | self.ReptitionControl = 0 73 | 74 | 75 | def wheelEvent(self, event): 76 | 77 | if self.ControlDown: 78 | 79 | if self.ReptitionControl == 0: 80 | 81 | delta = event.angleDelta().y() 82 | 83 | if delta > 0: 84 | self.zoom_in(0.05) 85 | else: 86 | self.zoom_out(0.05) 87 | 88 | self.ReptitionControl += 1 89 | 90 | if self.ReptitionControl == 10: 91 | self.ReptitionControl = 0 92 | 93 | 94 | 95 | 96 | 97 | def zoomVal(self, target): 98 | 99 | scale = target/self.ZoomFactor 100 | 101 | self.ApplyZoom(scale) 102 | 103 | 104 | def zoom_in(self, inc = 0.2): 105 | 106 | # Lets try to increase in 10% 107 | target = self.ZoomFactor+inc 108 | scale = target/self.ZoomFactor 109 | 110 | if self.ZoomFactor > 1.8: 111 | scale = 1 112 | 113 | self.ApplyZoom(scale) 114 | 115 | 116 | def zoom_out(self, inc = 0.2): 117 | 118 | # Lets try to decrease in 10% 119 | target = self.ZoomFactor-inc 120 | 121 | scale = target/self.ZoomFactor 122 | 123 | if self.ZoomFactor <= 0.6: 124 | scale = 1 125 | 126 | self.ApplyZoom(scale) 127 | 128 | 129 | @QtCore.pyqtSlot() 130 | def ApplyZoom(self, scale): 131 | # self.ZoomFactor = round(self.ZoomFactor - 0.1 , 2) 132 | 133 | # if self.ZoomFactor < 0.2: 134 | # self.ZoomFactor = 0.2 135 | self.ZoomFactor = self.ZoomFactor*scale 136 | 137 | rounded = round(self.ZoomFactor, 1) 138 | 139 | if abs( self.ZoomFactor - rounded ) < 0.01 : 140 | self.ZoomFactor = rounded 141 | 142 | 143 | 144 | 145 | # print(self.ZoomFactor) 146 | 147 | scale_tr = QtGui.QTransform() 148 | scale_tr.scale(scale, scale) 149 | 150 | tr = self._view.transform() * scale_tr 151 | self._view.setTransform(tr) 152 | 153 | self.UpdatedZoom.emit() 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acroper/BeamerQt/78109c29850ae49eff1a2f624b31a53acdbd6686/icon.ico -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Beamer QT 3 | Copyright (C) 2024 Jorge Guerrero - acroper@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | 20 | 21 | from PyQt6 import QtWidgets 22 | from gui.mainwindow import * 23 | import shutil 24 | 25 | 26 | def main(): 27 | 28 | # Launching GUI 29 | app = QtWidgets.QApplication(sys.argv) 30 | window = MainWindow() 31 | 32 | 33 | app.aboutToQuit.connect(app.deleteLater) 34 | app.exec() 35 | 36 | # Closing and deleting temporal folder 37 | try: 38 | shutil.rmtree(window.WorkDirectory) 39 | except: 40 | None 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /templates/annarbor.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/antibes.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/bergen.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/berkeley.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/berlin.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/boadilla.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/cambridgeus.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/copenhagen.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/darmstadt.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/default.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/dresden.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/frankfurt.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/genTemplate.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | 5 | tfile = open("templatelist.txt", "r") 6 | 7 | lista = tfile.readlines() 8 | 9 | tfile.close() 10 | 11 | for line in lista: 12 | 13 | line = line.replace("\n","") 14 | 15 | filename = line.lower() + ".xml" 16 | 17 | outputfile = open(filename, "w") 18 | 19 | outputfile.write("\n") 24 | 25 | 26 | outputfile.close() 27 | -------------------------------------------------------------------------------- /templates/goettingen.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/hannover.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/ilmenau.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/juanlespins.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/luebeck.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/madrid.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/malmoe.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/marburg.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/montpellier.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/paloalto.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/pittsburgh.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/rochester.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/singapore.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/szeged.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/templatelist.txt: -------------------------------------------------------------------------------- 1 | default 2 | Darmstadt 3 | Malmoe 4 | AnnArbor 5 | Dresden 6 | Marburg 7 | Antibes 8 | Frankfurt 9 | Montpellier 10 | Bergen 11 | Goettingen 12 | PaloAlto 13 | Berkeley 14 | Hannover 15 | Pittsburgh 16 | Berlin 17 | Ilmenau 18 | Rochester 19 | Boadilla 20 | JuanLesPins 21 | Singapore 22 | CambridgeUS 23 | Luebeck 24 | Szeged 25 | Copenhagen 26 | Madrid 27 | Warsaw -------------------------------------------------------------------------------- /templates/warsaw.xml: -------------------------------------------------------------------------------- 1 | 6 | --------------------------------------------------------------------------------