Objective: To have a slider that:
Has (2) slider handles
Can accept int and float values when defining the slider domain
Can step in non-integer increments
Can be defined over a domain having negative values
Attempt: The code I've tried is below. Further, I was able to find a package qtrangeslider, pip install qtrangeslider. The package nearly covers all of my objectives.
Problem: The package qtrangeslider falls short when handling domains having negative values. The package is unable to accurately catch mouse clicks on the slider handles when dealing with a domain covering negative values.
Question: Would any one be skilled enough to track down why the package is failing and/or suggest a different package that meets my stated objectives.
'''
''' Begin : Import source dependencies '''
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore, QtGui, QtWidgets
from qtrangeslider import QLabeledDoubleRangeSlider
''' End : Import source dependencies '''
''' Begin : Class definition of double handle slider '''
class AppDoubleRangeSlider( QtWidgets.QWidget ):
# Set the default style sheet definition
defaultStyleSpec = """
QSlider {
min-height: 10px;
}
QSlider::groove:horizontal {
border: 3px;
background: qradialgradient(cx:0, cy:0, radius: 1.2, fx:0.5, fy:0.5, stop:0 #B0C4DE, stop:1 #808080);
height: 20px;
margin: 15px 0;
border-radius: 10px;
}
QSlider::handle {
background: qradialgradient(cx:0, cy:0, radius: 1.2, fx:0.5, fy:0.5, stop:0 #00BFFF, stop:1 #1E90FF);
height: 20px;
width: 20px;
border-radius: 10px;
}
/*
"QSlider::sub-page" is the one exception ...
(it styles the area to the left of the QSlider handle)
*/
QSlider::sub-page:horizontal {
background: #32CD32;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
}
/*
for QRangeSlider: use "qproperty-barColor". "sub-page" will not work.
*/
QSlider {
qproperty-barColor: #32CD32;
}
"""
''' Begin : Class constructor '''
def __init__(self, parent=None, minSliderVal=0.0, maxSliderVal=1.0, handleLeftValue=0.0, handleRightValue=1.0):
# Call the parent constructor
super( AppDoubleRangeSlider, self ).__init__( parent )
# Track the inputs
self.minSliderVal = minSliderVal
self.maxSliderVal = maxSliderVal
self.handleLeftValue = handleLeftValue
self.handleRightValue = handleRightValue
# Initialize class member defaults
self.initClassDefaults()
# Set window properties
self.initWindowProperties()
# Initialize the application layout space
self.initLayoutSpace()
# Initialize the widgets
self.initWidgets()
# Finalize layout settings
self.finalizeLayout()
''' End : Constructor '''
''' Begin : Method to set window properties '''
def initClassDefaults(self):
# Initialize a double handed slider with input parameters
self.appSlider = QLabeledDoubleRangeSlider( QtCore.Qt.Horizontal )
self.appSlider.setStyleSheet( self.defaultStyleSpec )
self.appSlider.setRange( self.minSliderVal, self.maxSliderVal )
self.appSlider.setMinimum( self.minSliderVal )
self.appSlider.setMaximum( self.maxSliderVal )
self.appSlider.setValue( (self.handleLeftValue, self.handleRightValue) )
# Connect the slider to a callback to handle value changes
self.appSlider.valueChanged.connect( self.callbackValueChange )
''' End : Method to set window properties '''
''' Begin : Method to return slider handle values '''
def getSliderValues(self):
print((self.handleLeftValue, self.handleRightValue),'\n\n')
# Return the slider's handle values
return (self.handleLeftValue, self.handleRightValue)
''' End : Method to return slider handle values '''
''' Begin : Method to respond to value changes '''
def callbackValueChange(self, value):
print((self.handleLeftValue, self.handleRightValue),'\n\n')
# Track the slider's handle values
self.handleLeftValue = value[ 0 ]
self.handleRightValue = value[ 1 ]
''' End : Method to respond to value changes '''
''' Begin : Method to set window properties '''
def initWindowProperties(self):
# Define the minimum app size
self.minAppWidth = 300
self.minAppHeight = 100
# Set minimum size constraints on the window
self.setMinimumWidth( self.minAppWidth )
self.setMinimumHeight( self.minAppHeight )
# Set initial window size
self.resize( self.minAppWidth, self.minAppHeight )
''' End : Method to set window properties '''
''' Begin : Method to initialize the application layout space '''
def initLayoutSpace(self):
# Initialize a layout
self.layoutRoot = QtWidgets.QHBoxLayout()
''' End : Method to initialize the application layout space '''
''' Begin : Method to initialize the application layout space '''
def initWidgets(self):
pass
''' End : Method to initialize the application layout space '''
''' Begin : Method to finalize layout settings '''
def finalizeLayout(self):
# Push the slider to the layout
self.layoutRoot.addWidget( self.appSlider )
# Finalize window layout
self.setLayout( self.layoutRoot )
''' End : Method to finalize layout settings '''
''' End : Class definition of double handle slider '''
from qtrangeslider import QDoubleRangeSlider
from PyQt5 import QtCore, QtGui, QtWidgets
app = QApplication( [ ] )
myApp = AppDoubleRangeSlider( minSliderVal=-1, maxSliderVal=1, handleLeftValue=-0.5, handleRightValue=0.5 )
myApp.show()
app.exec_()
'''
I been trying an image to a Label via setPixmap() but to no avail...
In fact, i want that if i click to call the Methode def click_photo(self): to setthe image also
from PyQt5.QtWidgets import *
from PyQt5.QtMultimedia import *
from PyQt5.QtMultimediaWidgets import *
import os, sys, time
class MainWindow(QMainWindow):
def __init__(self): # constructor
super().__init__()
self.setGeometry(100, 100, 800, 600)
self.setStyleSheet("background : lightgrey;")
self.available_cameras = QCameraInfo.availableCameras()
self.Label_preview = QLabel(self, 'preview here')
self.status = QStatusBar()
self.status.setStyleSheet("background : white;")
self.setStatusBar(self.status) # adding status bar to the main window
self.save_path = "" # path to save
self.viewfinder = QCameraViewfinder() # creating a QCameraViewfinder object
self.viewfinder.show() # showing this viewfinder
self.setCentralWidget(self.viewfinder) # making it central widget of main window
self.select_camera(0) # Set the default camera.
toolbar = QToolBar("Camera Tool Bar") # creating a tool bar
self.addToolBar(toolbar) # adding tool bar to main window
click_action = QAction("Click photo", self) # creating a photo action to take photo
click_action.setStatusTip("This will capture picture") # adding status tip to the photo action
click_action.setToolTip("Capture picture")
click_action.triggered.connect(self.click_photo) # adding action to it
toolbar.addAction(click_action) # adding this to the tool bar
change_folder_action = QAction("Change save location", self) # similarly creating action for changing save folder
change_folder_action.setStatusTip("Change folder where picture will be saved saved.")
change_folder_action.setToolTip("Change save location") # adding tool tip to it
# setting calling method to the change folder action
# when triggered signal is emitted
change_folder_action.triggered.connect(self.change_folder)
toolbar.addAction(change_folder_action) # adding this to the tool bar
# creating a combo box for selecting camera
camera_selector = QComboBox()
# adding status tip to it
camera_selector.setStatusTip("Choose camera to take pictures")
# adding tool tip to it
camera_selector.setToolTip("Select Camera")
camera_selector.setToolTipDuration(2500)
# adding items to the combo box
camera_selector.addItems([camera.description()
for camera in self.available_cameras])
# adding action to the combo box
# calling the select camera method
camera_selector.currentIndexChanged.connect(self.select_camera)
# adding this to tool bar
toolbar.addWidget(camera_selector)
toolbar.setStyleSheet("background : white;")
self.setWindowTitle("PyQt5 Cam")
self.show()
# method to select camera
def select_camera(self, i):
self.camera = QCamera(self.available_cameras[i]) # getting the selected camera
self.camera.setViewfinder(self.viewfinder) # getting the selected camera
self.camera.setCaptureMode(QCamera.CaptureStillImage) # setting capture mode to the camera
self.camera.error.connect(lambda: self.alert(self.camera.errorString())) # if any error occur show the alert
self.camera.start() # start the camera
self.capture = QCameraImageCapture(self.camera) # creating a QCameraImageCapture object
self.capture.error.connect(lambda error_msg, error, msg: self.alert(msg)) # showing alert if error occur
self.capture.imageCaptured.connect(lambda d,
i: self.status.showMessage("Image captured : "
+ str(self.save_seq))) # when image captured showing message
self.capture.imageCaptured.connect(lambda d, i: self.status.showMessage("Image captured : " + str(self.save_seq))) # when image captured showing message
self.tipamu = i
# getting current camera name
self.current_camera_name = self.available_cameras[i].description()
# inital save sequence
self.save_seq = 0
# method to take photo
def click_photo(self):
# time stamp
timestamp = time.strftime("%d-%b-%Y-%H_%M_%S")
self.capture.capture(os.path.join(self.save_path,
"%s-%04d-%s.jpg" % (
self.current_camera_name,
self.save_seq,
timestamp
))) # capture the image and save it on the save path
# increment the sequence
self.save_seq += 1
# change folder method
def change_folder(self):
path = QFileDialog.getExistingDirectory(self, "Picture Location", "") # open the dialog to select path
if path: # if path is selected
self.save_path = path # update the path
self.save_seq = 0 # update the sequence
def alert(self, msg):
error = QErrorMessage(self) # error message
error.showMessage(msg) # setting text to the error message
# Driver code
if __name__ == "__main__" :
App = QApplication(sys.argv) # create pyqt5 app
window = MainWindow() # create the instance of our Window
sys.exit(App.exec()) # start the app
i tried
self.Label_preview.setPixmap(QPixmap(self.capture))
but it didn't work, is there a way to approach this: setting the image captured via webcam to the preview Qlabel self.Label_preview before saving that image to the disk or without saving to the disk ?
I started from the same example you found. Here is how I did it with a bit of extra styling and ported to PyQt6.
# importing required libraries
from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
from PyQt6.QtMultimedia import *
from PyQt6.QtMultimediaWidgets import *
from PyQt6.QtCore import Qt
import os
import sys
import time
stylesheet = """
QWidget {
background-color: rgba(32.000, 33.000, 36.000, 1.000);
color: rgba(170.000, 170.000, 170.000, 1.000);
selection-background-color: rgba(138.000, 180.000, 247.000, 1.000);
selection-color: rgba(32.000, 33.000, 36.000, 1.000);
}
QWidget:disabled {
color: rgba(105.000, 113.000, 119.000, 1.000);
selection-background-color: rgba(83.000, 87.000, 91.000, 1.000);
selection-color: rgba(105.000, 113.000, 119.000, 1.000);
}
QToolTip {
background-color: rgba(41.000, 42.000, 45.000, 1.000);
color: rgba(228.000, 231.000, 235.000, 1.000);
border: 1px solid rgba(63.000, 64.000, 66.000, 1.000);
}
QSizeGrip {
width: 0;
height: 0;
image: none;
}
QStatusBar {
background-color: rgba(42.000, 43.000, 46.000, 1.000);
}
QStatusBar::item {
border: none;
}
QStatusBar QWidget {
background-color: transparent;
padding: 0px;
border-radius: 0px;
margin: 0px;
}
QStatusBar QWidget:pressed {
background-color: rgba(79.000, 80.000, 84.000, 1.000);
}
QStatusBar QWidget:disabled {
background-color: rgba(32.000, 33.000, 36.000, 1.000);
}
QStatusBar QWidget:checked {
background-color: rgba(79.000, 80.000, 84.000, 1.000);
}
QToolBar {
background-color: rgba(41.000, 42.000, 45.000, 1.000);
padding: 1x;
font-weight: bold;
spacing: 1px;
margin: 1px;
}
QToolBar::separator {
background-color: rgba(63.000, 64.000, 66.000, 1.000);
}
QToolBar::separator:horizontal {
width: 2px;
margin: 0 6px;
}
QToolBar::separator:vertical {
height: 2px;
margin: 6px 0;
}
QPushButton {
border: 1px solid rgba(63.000, 64.000, 66.000, 1.000);
padding: 4px 8px;
border-radius: 4px;
color: rgba(138.000, 180.000, 247.000, 1.000);
}
QPushButton:hover {
background-color: rgba(30.000, 43.000, 60.000, 1.000);
}
QPushButton:pressed {
background-color: rgba(46.000, 70.000, 94.000, 1.000);
}
QPushButton:checked {
border-color: rgba(138.000, 180.000, 247.000, 1.000);
}
QPushButton:disabled {
border-color: rgba(63.000, 64.000, 66.000, 1.000);
}
QPushButton[flat=true]:!checked {
border-color: transparent;
}
QDialogButtonBox QPushButton {
min-width: 65px;
}
QComboBox {
border: 1px solid rgba(63.000, 64.000, 66.000, 1.000);
border-radius: 4px;
min-height: 1.5em;
padding: 0 4px;
background-color: rgba(63.000, 64.000, 66.000, 1.000);
}
QComboBox:focus,
QComboBox:open {
border: 1px solid rgba(138.000, 180.000, 247.000, 1.000);
}
QComboBox::drop-down {
subcontrol-position: center right;
border: none;
padding-right: 4px;
}
QComboBox::item:selected {
border: none;
background-color: rgba(0.000, 72.000, 117.000, 1.000);
color: rgba(228.000, 231.000, 235.000, 1.000);
}
QComboBox QAbstractItemView {
margin: 0;
border: 1px solid rgba(63.000, 64.000, 66.000, 1.000);
selection-background-color: rgba(0.000, 72.000, 117.000, 1.000);
selection-color: rgba(228.000, 231.000, 235.000, 1.000);
padding: 2px;
}
"""
# Main window class
class MainWindow(QMainWindow):
# constructor
def __init__(self):
super().__init__()
self.captured_image = None
self.save_seq = None
self.capture = None
self.camera = None
self.current_camera_name = None
self.mirror_h = True
# setting geometry
self.setGeometry(200, 200, 800, 600)
self.setStyleSheet("background : darkgrey;")
# getting available cameras
self.available_cameras = QMediaDevices.videoInputs()
# if no camera found
if not self.available_cameras:
# exit the code
sys.exit()
self.status = QStatusBar()
self.setStatusBar(self.status)
# path to save
self.save_path = ""
toolbar = QToolBar("Camera Tool Bar")
self.addToolBar(toolbar)
# creating a photo action to take photo
click_action = QAction("Get Photo", self)
click_action.setStatusTip("This will capture picture")
click_action.setToolTip("Capture picture")
click_action.triggered.connect(self.capture_picture)
toolbar.addAction(click_action)
# similarly creating action for changing save folder
change_folder_action = QAction("Save location", self)
change_folder_action.setStatusTip("Change folder where picture will be saved saved.")
change_folder_action.setToolTip("Change save location")
change_folder_action.triggered.connect(self.change_folder)
toolbar.addAction(change_folder_action)
# creating a combo box for selecting camera
camera_selector = QComboBox()
camera_selector.setStatusTip("Choose camera to take pictures")
camera_selector.setToolTip("Select Camera")
camera_selector.setToolTipDuration(2500)
camera_selector.addItems([camera.description() for camera in self.available_cameras])
camera_selector.currentIndexChanged.connect(self.select_camera)
toolbar.addWidget(camera_selector)
camera_mirror = QCheckBox("Mirror")
camera_mirror.setChecked(True)
camera_mirror.setStatusTip("Mirror the captured image horizontally")
camera_mirror.setToolTip("Mirror Camera")
camera_mirror.stateChanged.connect(self.on_mirror_changed)
toolbar.addWidget(camera_mirror)
# setting window title
self.setWindowTitle("PyQt6 Cam")
main_wdg = QWidget()
layout = QVBoxLayout(main_wdg)
glay = QGridLayout()
glay.setRowStretch(0, 1)
glay.setRowStretch(1, 0)
self.label = QLabel()
self.label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
self.save_btn = QPushButton("Save Picture")
self.save_btn.clicked.connect(self.on_save_picture)
glay.addWidget(self.label, 0, 0)
glay.addWidget(self.save_btn, 1, 0)
self.video_wdg = QVideoWidget()
hlay = QHBoxLayout()
hlay.addLayout(glay)
hlay.addWidget(self.video_wdg)
layout.addLayout(hlay, stretch=1)
self.setCentralWidget(main_wdg)
self.video_wdg.show()
# showing the main window
self.show()
self.select_camera(0)
# method to select camera
def select_camera(self, current_camera):
media_capture_session = QMediaCaptureSession(self)
self.camera = QCamera(self.available_cameras[current_camera])
self.camera.start()
media_capture_session.setCamera(self.camera)
media_capture_session.setVideoOutput(self.video_wdg)
self.camera.errorOccurred.connect(lambda err, err_str: self.alert(err_str))
self.capture = QImageCapture(self.camera)
media_capture_session.setImageCapture(self.capture)
self.capture.errorOccurred.connect(lambda error_msg, error, msg: self.alert(msg))
# when image captured showing message
self.capture.imageCaptured.connect(self.on_image_captured)
self.current_camera_name = self.available_cameras[current_camera].description()
# initial save sequence
self.save_seq = 0
def on_image_captured(self, id, image):
width = self.label.width()
height = self.label.height()
self.captured_image = image
if self.mirror_h is True:
self.captured_image = image.mirrored(horizontal=True, vertical=False)
pixmap = QPixmap().fromImage(self.captured_image)
self.label.setPixmap(pixmap.scaled(width, height, Qt.AspectRatioMode.KeepAspectRatio))
self.status.showMessage("Image captured.")
# method to take photo
def capture_picture(self):
self.capture.capture()
def on_save_picture(self):
# time stamp
timestamp = time.strftime("%d-%b-%Y-%H_%M_%S")
file_path = os.path.join(
self.save_path, "%s-%04d-%s.jpg" % (self.current_camera_name, self.save_seq, timestamp))
try:
self.captured_image.save(file_path, format='jpg', quality=-1)
except Exception as err:
print(err)
self.status.showMessage("Image saved to: %s" % str(file_path))
# increment the sequence
self.save_seq += 1
def on_mirror_changed(self, state):
self.mirror_h = True if int(state) else False
# change folder method
def change_folder(self):
# open the dialog to select path
path = QFileDialog.getExistingDirectory(self, "Picture Location", "")
# if path is selected
if path:
# update the path
self.save_path = path
# update the sequence
self.save_seq = 0
# method for alerts
def alert(self, msg):
# error message
error = QErrorMessage(self)
# setting text to the error message
error.showMessage(msg)
# Driver code
if __name__ == "__main__":
# create pyqt5 app
App = QApplication(sys.argv)
# create the instance of our Window
window = MainWindow()
window.setStyleSheet(stylesheet)
# start the app
sys.exit(App.exec())
wxWidget::wxWebView->Find - https://docs.wxwidgets.org/3.0/classwx_web_view.html#ad85a7aa0351b6e6a6bffd4220f9758ee
Sample Code
wxWebView *webView;
webView = wxWebView::New(this, wxID_ANY);
webView->SetPage("<html><head><meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no' /><meta content='en-us' http-equiv=\"Content-Language\" /><meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\" /><title>Demo App</title><style type=\"text/css\">.auto-style8 {font-family: \"Bookman Old Style\";font-weight:bold;color:#734024;}.underline{color:#ff0000;}.text {color:#230fd2;}#wrapper1 { width: 100% ; display: table;}#wrapper {width: 100% ; display: table;}#header1 {color: #E6F0F1; display: table; background-color: #0000ff;width: 100% ; text-align: center;height: 50px;font-size: 105% ; font-weight: bold;}#header2 {background-color: #B98264;display: table; width: 100% ; height: 40px;text-align: center;color: #FFFFFF; font-size: 75% ; font-weight: bold;}.auto-style13 {text-decoration: underline;}.text2{color:#000000;text-align: justify;}</style></head> <body style=\"color:#000000;background-color:#FFFFFF\"><div id=\"header2\"><p style=\"font-size: x-medium\" class=\"auto-style13\">Demo Page<br></p></div> <p class=\"auto-style8\" style=\"width: 100%;\">To Start - <br/><br/>STEP 1. Select data</p></body></html>","");
long lMatchCount = webView->Find("a",wxWEBVIEW_FIND_HIGHLIGHT_RESULT );
if(lMatchCount == wxNOT_FOUND){
wxMessageBox("Search not found");
}else{
//wxMessageBox("Search found" + lMatchCount);
wxLogMessage("Matches: %s ",std::to_string(lMatchCount));
}
Above code works correctly and returns number of occurrences of "a".
When webview->SetPage is updated again dynamically with large text ( any dummy content will do ) and if find call is called again it crashes with Segmentation fault
If SetPage is loaded with large text in 1st instance itself it crashes as well so ruled out possibility of update causing the crash.
Also tried loading large file from local file ( default.html contains above html code - replace any long string and it crashes with same result )
wxFile fFileIn("default.html", wxFile::read);
wxFileInputStream in(fFileIn);
webView->SetPage(in,"");
fFileIn.Close();
Similar issue is reported here http://trac.wxwidgets.org/ticket/15207
As per discussion on this thread http://trac.wxwidgets.org/ticket/15207.
We have workaround to this problem ( Still don't know why this is happening and what is the root cause )
Use the code mentioned in PR https://github.com/wxWidgets/wxWidgets/pull/2626.
Call Find before the setPage
wxFile fFileIn("default.html", wxFile::read);
wxFileInputStream in(fFileIn);
webView->Find("");
webView->SetPage(in,"");
fFileIn.Close();
I have multiple selectizeInput in my Shiny app. Most of them are not supposed to be full of variables/elements, but one of them yes. The problem is the more variables/elements in the box, the larger is this one and the display is not good at all. I have found solutions to manipulate the height, font, width, etc. of a input widget:
library(shiny)
ui <- fluidPage(
fluidRow(
selectInput("speed", label=NULL, choices = list("1" = 1, "2" = 2), selected = 1),
tags$head(tags$style(HTML(".selectize-input {height: 100px; width: 500px; font-size: 100px;}")))
)
)
server <- function(input, output){}
shinyApp(ui, server)
This works. But this solution affects to all the selectizeInput I have in my app, I'm interested in just target one selectizeInput. Is there a way to do that?
You can use some advanced CSS to select the .selectize-input box. So in selectInput structure, the element with the actual id is assigned to a select tag and the box you want is the first child of the following tag after the select tag. We can use + to select the following tag and use > to select the first child containing the .selectize-input class of the following tag.
library(shiny)
ui <- fluidPage(
tags$head(tags$style(HTML("#speed + div > .selectize-input {height: 100px; width: 500px; font-size: 100px;}"))),
fluidRow(
selectInput("speed", label=NULL, choices = list("1" = 1, "2" = 2), selected = 1),
selectInput("speed2", label=NULL, choices = list("1" = 1, "2" = 2), selected = 1)
)
)
server <- function(input, output){}
shinyApp(ui, server)
#ID + div > .selectize-input is what you want to apply.
Try to run my example, I created two selectInput and only the first one will have the CSS style.
I have added a print button and a PrintDialog to my custom app and now need to write a print function.
Do I need to open up a new window and build a table containing the grid data formatted with css styles fitted to A4 paper size or is there something built into Rally that I can use?
I am new to Rally and Ext JS, so any advice would be appreciated!
Ext.create('Rally.ui.dialog.PrintDialog', {
height: 250,
autoShow: true,
autoCenter: false,
shouldShowFormatOptions: false,
defaultTitle: 'Book Of Work Report',
listeners: {
print: function(event) {
//How do I print a grid?
},
To print the grid, I created a Rally.data.WsapiDataStore and loaded the store into an array. I passed this array(StoreData) to the below function which opens a print window displaying the grid in a table.
_printDetails: function(Storedata) {
var myData = Storedata;
var htmlTable ='<table>';
htmlTable +='<width="100%">';
var r,c;
for(r= 0 ; r<myData.length; r++){
htmlTable+= '<tr>';
for(c = 0 ; c<myData[0].length; c++){
htmlTable+='<td>'+myData[r][c]+'</td>';
}
htmlTable+='</tr>';
}
htmlTable+='</table>';
var cssTable = '<style type="text/css">';
cssTable +='table {border-collapse:collapse;...}';
cssTable +='th {color:#080808;border-bottom-style: solid; ...}';
cssTable +='tr {color:#000000; border-bottom-style: solid; ..}';
cssTable +='td {padding:3px 4px; text-align:left; vertical-align:top;}';
cssTable +='#filter {text-align:left; ...}';
cssTable += '</style>';
var printwindow=window.open('', '', 'width=1000,height=500');
var myDate = new Date;
printwindow.document.write('<div id="todayDate">' + Ext.Date.format(myDate,'F j, Y, g:i a') + '</div>');
printwindow.document.write('<div id="header">Book Of Work Report</div>');
printwindow.document.write('<div id="filter"><p>' + this._GetFilterString() + '</p></div>');
printwindow.document.write(htmlTable);
printwindow.document.write(cssTable);
printwindow.document.close();
printwindow.focus();
printwindow.print();
printwindow.close();
},
Your inclination is correct - the best option here is to open a window that you populate with table output/grid content and apply whatever CSS formatting you prefer. Rally doesn't have any server-side printing output functionality (at least that is exposed to AppSDK2).