OpenShot Video Editor  2.0.0
tutorial.py
Go to the documentation of this file.
1 ##
2 #
3 # @file
4 # @brief This file contains the tutorial dialogs, which are used to explain certain features to new users
5 # @author Jonathan Thomas <jonathan@openshot.org>
6 #
7 # @section LICENSE
8 #
9 # Copyright (c) 2008-2016 OpenShot Studios, LLC
10 # (http://www.openshotstudios.com). This file is part of
11 # OpenShot Video Editor (http://www.openshot.org), an open-source project
12 # dedicated to delivering high quality video editing and animation solutions
13 # to the world.
14 #
15 # OpenShot Video Editor is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
19 #
20 # OpenShot Video Editor is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
27 #
28 
29 import functools
30 
31 from PyQt5.QtCore import Qt, QPoint, QRectF, QEvent
32 from PyQt5.QtGui import *
33 from PyQt5.QtWidgets import QLabel, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QToolButton
34 
35 from classes.logger import log
36 from classes.settings import get_settings
37 from classes.app import get_app
38 
39 
40 ##
41 # A QWidget used to instruct a user how to use a certain feature
42 class TutorialDialog(QWidget):
43 
44  ##
45  # Custom paint event
46  def paintEvent(self, event, *args):
47  # Paint custom frame image on QWidget
48  painter = QPainter(self)
49  painter.setRenderHint(QPainter.Antialiasing)
50 
51  # Paint blue rounded rectangle
52  path = QPainterPath()
53  path.addRoundedRect(QRectF(31, 0, self.width()-31, self.height()), 10, 10)
54  painter.setPen(Qt.NoPen)
55  painter.fillPath(path, QColor("#53a0ed"))
56  painter.drawPath(path)
57 
58  # Paint gray rounded rectangle
59  path = QPainterPath()
60  path.addRoundedRect(QRectF(32, 1, self.width()-33, self.height()-2), 10, 10)
61  painter.setPen(Qt.NoPen)
62  painter.fillPath(path, QColor("#424242"))
63  painter.drawPath(path)
64 
65  # Paint blue triangle
66  arrow_height = 20
67  path = QPainterPath()
68  path.moveTo (0, 35)
69  path.lineTo (31, 35 - arrow_height)
70  path.lineTo (31, (35 - arrow_height) + (arrow_height * 2))
71  path.lineTo (0, 35)
72  painter.fillPath(path, QColor("#53a0ed"))
73  painter.drawPath(path)
74 
75  def eventFilter(self, object, e):
76  if e.type() == QEvent.WindowActivate:
77  # Raise parent window, and then this tutorial
78  get_app().window.showNormal()
79  get_app().window.raise_()
80  self.raise_()
81 
82  return False
83 
84  ##
85  # Move widget next to its position widget
86  def moveWidget(self):
87  x = self.position_widget.mapToGlobal(self.position_widget.pos()).x()
88  y = self.position_widget.mapToGlobal(self.position_widget.pos()).y()
89  self.move(QPoint(x + self.x_offset, y + self.y_offset))
90 
91  def __init__(self, text, position_widget, x_offset, y_offset, *args):
92  # Invoke parent init
93  QWidget.__init__(self, *args)
94 
95  # get translations
96  app = get_app()
97  _ = app._tr
98 
99  # Keep track of widget to position next to
100  self.position_widget = position_widget
101  self.x_offset = x_offset
102  self.y_offset = y_offset
103 
104  # Create vertical box
105  vbox = QVBoxLayout()
106  vbox.setContentsMargins(32,10,10,10)
107 
108  # Add label
109  self.label = QLabel(self)
110  self.label.setText(text)
111  self.label.setTextFormat(Qt.RichText)
112  self.label.setWordWrap(True)
113  #self.label.setMargin(10)
114  self.label.setStyleSheet("margin: 10px; margin-left: 20px")
115  vbox.addWidget(self.label)
116 
117  # Add button box
118  hbox = QHBoxLayout()
119  hbox.setContentsMargins(20,0,0,0)
120 
121  # Create buttons
122  self.btn_close_tips = QPushButton(self)
123  self.btn_close_tips.setText(_("Hide Tutorial"))
124  self.btn_next_tip = QPushButton(self)
125  self.btn_next_tip.setText(_("Next"))
126  self.btn_next_tip.setStyleSheet("font-weight:bold;")
127  hbox.addWidget(self.btn_close_tips)
128  hbox.addWidget(self.btn_next_tip)
129  vbox.addLayout(hbox)
130 
131  # Set layout
132  self.setLayout(vbox)
133 
134  # Set size
135  self.setMinimumWidth(350)
136  self.setMinimumHeight(100)
137 
138  # Make it's own window
139  self.setWindowTitle("Tutorial")
140  self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint)
141  self.setAttribute(Qt.WA_TranslucentBackground, True)
142  self.setFocusPolicy(Qt.NoFocus)
143 
144  # Position window next to other widget
145  self.moveWidget()
146 
147  # Install event filter
148  self.installEventFilter(self)
149 
150 
151 ##
152 # Manage and present a list of tutorial dialogs
154 
155  ##
156  # Process and show the first non-completed tutorial
157  def process(self, parent_name=None):
158  log.info("process tutorial dialogs")
159 
160  # Do nothing if a tutorial is already visible
161  if self.current_dialog:
162  self.re_show_dialog()
163  return
164 
165  # Loop through and add each tutorial dialog
166  for tutorial_details in self.tutorial_objects:
167  # Get details
168  tutorial_id = tutorial_details["id"]
169  tutorial_object = tutorial_details["object"]
170  tutorial_text = tutorial_details["text"]
171  tutorial_x_offset = tutorial_details["x"]
172  tutorial_y_offset = tutorial_details["y"]
173 
174  # Skip completed tutorials (and invisible widgets)
175  if tutorial_object.visibleRegion().isEmpty() or tutorial_id in self.tutorial_ids or not self.tutorial_enabled:
176  continue
177 
178  # Create tutorial
179  tutorial_dialog = TutorialDialog(tutorial_text, tutorial_object, tutorial_x_offset, tutorial_y_offset)
180 
181  # Connect signals
182  tutorial_dialog.btn_next_tip.clicked.connect(functools.partial(self.next_tip, tutorial_id))
183  tutorial_dialog.btn_close_tips.clicked.connect(functools.partial(self.hide_tips, tutorial_id, True))
184 
185  # Show dialog
186  self.current_dialog = tutorial_dialog
187  self.current_dialog.show()
188  break
189 
190  ##
191  # Mark the current tip completed, and show the next one
192  def next_tip(self, tid):
193  log.info("next_tip")
194 
195  # Hide matching tutorial
196  self.hide_tips(tid)
197 
198  # Process the next tipe
199  self.process()
200 
201  ##
202  # Hide the current tip, and don't show anymore
203  def hide_tips(self, tid, user_clicked=False):
204  log.info("hide_tips")
205  s = get_settings()
206 
207  # Loop through and find current tid
208  for tutorial_object in self.tutorial_objects:
209  # Get details
210  tutorial_id = tutorial_object["id"]
211  if tutorial_id == tid:
212  # Hide dialog
213  self.close_dialogs()
214  # Update settings that this tutorial is completed
215  if tid not in self.tutorial_ids:
216  self.tutorial_ids.append(str(tid))
217  s.set("tutorial_ids", ",".join(self.tutorial_ids))
218 
219  # Mark tutorial as completed (if settings)
220  if user_clicked:
221  # Disable all tutorials
222  self.tutorial_enabled = False
223  s.set("tutorial_enabled", False)
224 
225  ##
226  # Close any open tutorial dialogs
227  def close_dialogs(self):
228  if self.current_dialog:
229  self.current_dialog.hide()
230  self.current_dialog = None
231 
232  ##
233  # Disconnect from all signals, and shutdown tutorial manager
234  def exit_manager(self):
235  try:
236  self.win.dockFiles.visibilityChanged.disconnect()
237  self.win.dockTransitions.visibilityChanged.disconnect()
238  self.win.dockEffects.visibilityChanged.disconnect()
239  self.win.dockProperties.visibilityChanged.disconnect()
240  self.win.dockVideo.visibilityChanged.disconnect()
241  except:
242  # Ignore errors from this
243  pass
244 
245  # Close dialog window
246  self.close_dialogs()
247 
248  ##
249  # Re show an active dialog
250  def re_show_dialog(self):
251  if self.current_dialog:
252  self.current_dialog.showNormal()
253  self.current_dialog.raise_()
254 
255  ##
256  # Reposition a tutorial dialog next to another widget
258  if self.current_dialog:
259  self.current_dialog.moveWidget()
260 
261  ##
262  # Minimize any visible tutorial dialog
263  def minimize(self):
264  log.info("minimize tutorial")
265  if self.current_dialog:
266  self.current_dialog.showMinimized()
267 
268  ##
269  # Constructor
270  def __init__(self, win):
271  self.tutorials = []
272  self.win = win
273  self.current_dialog = None
274 
275  # get translations
276  app = get_app()
277  _ = app._tr
278 
279  # get settings
280  s = get_settings()
281  self.tutorial_enabled = s.get("tutorial_enabled")
282  self.tutorial_ids = s.get("tutorial_ids").split(",")
283 
284  # Find export toolbar button on main window
285  export_button = None
286  for toolbutton in self.win.toolBar.children():
287  if type(toolbutton) == QToolButton and toolbutton.defaultAction() and toolbutton.defaultAction().objectName() == "actionExportVideo":
288  export_button = toolbutton
289 
290  # Add all possible tutorials
291  self.tutorial_objects = [ {"id":"1", "x":20, "y":0, "object":self.win.filesTreeView, "text":_("<b>Project Files:</b> Get started with your project by adding video, audio, and image files here. Drag and drop files from your file system.")},
292  {"id":"2", "x":200, "y":-15, "object":self.win.timeline, "text":_("<b>Timeline:</b> Arrange your clips on the timeline here. Overlap clips to create automatic transitions. Access lots of fun presets and options by right-clicking on clips.")},
293  {"id":"3", "x":150, "y":100, "object":self.win.dockVideoContents, "text":_("<b>Video Preview:</b> Watch your timeline video preview here. Use the buttons (play, rewind, fast-forward) to control the video playback.")},
294  {"id":"4", "x":20, "y":-35, "object":self.win.propertyTableView, "text":_("<b>Properties:</b> View and change advanced properties of clips and effects here. Right-clicking on clips is usually faster than manually changing properties.")},
295  {"id":"5", "x":20, "y":10, "object":self.win.transitionsTreeView, "text":_("<b>Transitions:</b> Create a gradual fade from one clip to another. Drag and drop a transition onto the timeline and position it on top of a clip (usually at the beginning or ending).")},
296  {"id":"6", "x":20, "y":20, "object":self.win.effectsTreeView, "text":_("<b>Effects:</b> Adjust brigthness, contrast, saturation, and add exciting special effects. Drag and drop an effect onto the timeline and position it on top of a clip (or track)")},
297  {"id":"7", "x":-265, "y":-22, "object":export_button, "text":_("<b>Export Video:</b> When you are ready to create your finished video, click this button to export your timeline as a single video file.")}
298  ]
299 
300  # Connect to dock widgets
301  self.win.dockFiles.visibilityChanged.connect(functools.partial(self.process, "dockFiles"))
302  self.win.dockTransitions.visibilityChanged.connect(functools.partial(self.process, "dockTransitions"))
303  self.win.dockEffects.visibilityChanged.connect(functools.partial(self.process, "dockEffects"))
304  self.win.dockProperties.visibilityChanged.connect(functools.partial(self.process, "dockProperties"))
305  self.win.dockVideo.visibilityChanged.connect(functools.partial(self.process, "dockVideo"))
306 
307  # Process tutorials (1 by 1)
308  if self.tutorial_enabled:
309  self.process()
def get_app()
Returns the current QApplication instance of OpenShot.
Definition: app.py:54
def __init__(self, win)
Constructor.
Definition: tutorial.py:270
def close_dialogs(self)
Close any open tutorial dialogs.
Definition: tutorial.py:227
def re_show_dialog(self)
Re show an active dialog.
Definition: tutorial.py:250
def minimize(self)
Minimize any visible tutorial dialog.
Definition: tutorial.py:263
Manage and present a list of tutorial dialogs.
Definition: tutorial.py:153
A QWidget used to instruct a user how to use a certain feature.
Definition: tutorial.py:42
def process(self, parent_name=None)
Process and show the first non-completed tutorial.
Definition: tutorial.py:157
def hide_tips(self, tid, user_clicked=False)
Hide the current tip, and don&#39;t show anymore.
Definition: tutorial.py:203
def moveWidget(self)
Move widget next to its position widget.
Definition: tutorial.py:86
def exit_manager(self)
Disconnect from all signals, and shutdown tutorial manager.
Definition: tutorial.py:234
def paintEvent(self, event, args)
Custom paint event.
Definition: tutorial.py:46
def get_settings()
Get the current QApplication&#39;s settings instance.
Definition: settings.py:43
def re_position_dialog(self)
Reposition a tutorial dialog next to another widget.
Definition: tutorial.py:257
def __init__(self, text, position_widget, x_offset, y_offset, args)
Definition: tutorial.py:91
def eventFilter(self, object, e)
Definition: tutorial.py:75
def next_tip(self, tid)
Mark the current tip completed, and show the next one.
Definition: tutorial.py:192