├── 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&no_recurring=0&item_name=Support+the+development+of+BeamerQT&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 |
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 |
2 | AnnArbor
3 | AnnArbor
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/antibes.xml:
--------------------------------------------------------------------------------
1 |
2 | Antibes
3 | Antibes
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/bergen.xml:
--------------------------------------------------------------------------------
1 |
2 | Bergen
3 | Bergen
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/berkeley.xml:
--------------------------------------------------------------------------------
1 |
2 | Berkeley
3 | Berkeley
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/berlin.xml:
--------------------------------------------------------------------------------
1 |
2 | Berlin
3 | Berlin
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/boadilla.xml:
--------------------------------------------------------------------------------
1 |
2 | Boadilla
3 | Boadilla
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/cambridgeus.xml:
--------------------------------------------------------------------------------
1 |
2 | CambridgeUS
3 | CambridgeUS
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/copenhagen.xml:
--------------------------------------------------------------------------------
1 |
2 | Copenhagen
3 | Copenhagen
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/darmstadt.xml:
--------------------------------------------------------------------------------
1 |
2 | Darmstadt
3 | Darmstadt
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/default.xml:
--------------------------------------------------------------------------------
1 |
2 | default
3 | default
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/dresden.xml:
--------------------------------------------------------------------------------
1 |
2 | Dresden
3 | Dresden
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/frankfurt.xml:
--------------------------------------------------------------------------------
1 |
2 | Frankfurt
3 | Frankfurt
4 |
5 |
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")
20 | outputfile.write(""+line+"\n")
21 | outputfile.write(""+line+"\n")
22 | outputfile.write("\n")
23 | outputfile.write("\n")
24 |
25 |
26 | outputfile.close()
27 |
--------------------------------------------------------------------------------
/templates/goettingen.xml:
--------------------------------------------------------------------------------
1 |
2 | Goettingen
3 | Goettingen
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/hannover.xml:
--------------------------------------------------------------------------------
1 |
2 | Hannover
3 | Hannover
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/ilmenau.xml:
--------------------------------------------------------------------------------
1 |
2 | Ilmenau
3 | Ilmenau
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/juanlespins.xml:
--------------------------------------------------------------------------------
1 |
2 | JuanLesPins
3 | JuanLesPins
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/luebeck.xml:
--------------------------------------------------------------------------------
1 |
2 | Luebeck
3 | Luebeck
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/madrid.xml:
--------------------------------------------------------------------------------
1 |
2 | Madrid
3 | Madrid
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/malmoe.xml:
--------------------------------------------------------------------------------
1 |
2 | Malmoe
3 | Malmoe
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/marburg.xml:
--------------------------------------------------------------------------------
1 |
2 | Marburg
3 | Marburg
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/montpellier.xml:
--------------------------------------------------------------------------------
1 |
2 | Montpellier
3 | Montpellier
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/paloalto.xml:
--------------------------------------------------------------------------------
1 |
2 | PaloAlto
3 | PaloAlto
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/pittsburgh.xml:
--------------------------------------------------------------------------------
1 |
2 | Pittsburgh
3 | Pittsburgh
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/rochester.xml:
--------------------------------------------------------------------------------
1 |
2 | Rochester
3 | Rochester
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/singapore.xml:
--------------------------------------------------------------------------------
1 |
2 | Singapore
3 | Singapore
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/szeged.xml:
--------------------------------------------------------------------------------
1 |
2 | Szeged
3 | Szeged
4 |
5 |
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 |
2 | Warsaw
3 | Warsaw
4 |
5 |
6 |
--------------------------------------------------------------------------------